Backbone.js lấy và đặt thuộc tính đối tượng lồng nhau


105

Tôi có một câu hỏi đơn giản về các hàm getset của Backbone.js .

1) Với đoạn mã dưới đây, làm cách nào để tôi có thể 'lấy' hoặc 'đặt' obj1.myAttribute1 trực tiếp?

Câu hỏi khác:

2) Trong Mô hình, ngoài đối tượng mặc định , tôi có thể / nên khai báo các thuộc tính khác của mô hình ở đâu, sao cho chúng có thể được truy cập thông qua các phương thức get và set của Backbone?

var MyModel = Backbone.Model.extend({
    defaults: {
        obj1 : {
            "myAttribute1" : false,
            "myAttribute2" : true,
        }
    }
})

var MyView = Backbone.View.extend({
    myFunc: function(){
        console.log(this.model.get("obj1"));
        //returns the obj1 object
        //but how do I get obj1.myAttribute1 directly so that it returns false?
    }
});

Tôi biết tôi có thể làm:

this.model.get("obj1").myAttribute1;

nhưng đó có phải là thực hành tốt?


3
Mặc dù nó không phải là câu trả lời cho câu hỏi: Bất cứ khi nào chỉ định một đối tượng (bất kỳ thứ gì được truyền qua tham chiếu) trong defaults(obj1 trong trường hợp này), thì cùng một đối tượng đó sẽ được chia sẻ trên tất cả các trường hợp của mô hình. Thực tiễn hiện tại là định nghĩa defaultsnhư một hàm trả về một đối tượng được sử dụng làm giá trị mặc định. backbonejs.org/#Model-defaults (xem phần ghi chú in nghiêng)
Jonathan F

1
@JonathanF Các bình luận không dành cho câu trả lời vì vậy bạn không bao giờ cần khai báo :)
TJ

Câu trả lời:


144

Mặc dù this.model.get("obj1").myAttribute1ổn, nhưng nó có một chút vấn đề vì sau đó bạn có thể bị cám dỗ để làm cùng một loại điều cho bộ, tức là

this.model.get("obj1").myAttribute1 = true;

Nhưng nếu bạn làm điều này, bạn sẽ không nhận được những lợi ích của các mô hình Backbone myAttribute1, chẳng hạn như các sự kiện thay đổi hoặc xác thực.

Một giải pháp tốt hơn là không bao giờ lồng các POJSO ("các đối tượng JavaScript cũ thuần túy") trong các mô hình của bạn và thay vào đó lồng các lớp mô hình tùy chỉnh. Vì vậy, nó sẽ trông giống như sau:

var Obj = Backbone.Model.extend({
    defaults: {
        myAttribute1: false,
        myAttribute2: true
    }
});

var MyModel = Backbone.Model.extend({
    initialize: function () {
        this.set("obj1", new Obj());
    }
});

Sau đó, mã truy cập sẽ là

var x = this.model.get("obj1").get("myAttribute1");

nhưng quan trọng hơn, mã cài đặt sẽ là

this.model.get("obj1").set({ myAttribute1: true });

sẽ kích hoạt các sự kiện thay đổi thích hợp và những thứ tương tự. Ví dụ làm việc tại đây: http://jsfiddle.net/g3U7j/


24
Đối với câu trả lời này, tôi sẽ thêm lời khuyên rằng giải pháp này đề cập đến vấn đề vi phạm Luật Demeter phổ biến. Tôi sẽ xem xét việc thêm các phương thức tiện lợi ẩn điều hướng vào đối tượng lồng nhau. Về cơ bản, người gọi của bạn không cần biết cấu trúc bên trong của mô hình; sau tất cả, nó có thể thay đổi và những người gọi không phải là người khôn ngoan hơn.
Bill Eisenhauer

7
Không thể làm việc này cho tôi. Ném lỗi:Uncaught TypeError: Object #<Object> has no method 'set'
wilsonpage

1
@ChristianNunciato, pagewil, Benno: Có vẻ như bạn đã bỏ qua điểm của bài viết, đó là lồng các mô hình Backbone vào bên trong các mô hình Backbone. Không lồng các đối tượng đơn giản vào bên trong các mô hình Backbone. Ví dụ làm việc ở đây: jsfiddle.net/g3U7j
Domenic

1
Tôi đã không kiểm tra mã backbone.js, nhưng từ thử nghiệm của tôi, nếu bạn có Mô hình tùy chỉnh lồng nhau và thay đổi thuộc tính của nó bằng set (), thì mô hình mẹ của nó sẽ không tự kích hoạt sự kiện 'thay đổi'; Tôi đã phải tự mình khai hỏa sự kiện. Tôi thực sự chỉ nên kiểm tra mã, nhưng đây có phải là sự hiểu biết của bạn?
tom

2
@tom đó là chính xác. Backbone không phải là trường hợp đặc biệt khi thuộc tính của các mô hình là các phiên bản của Backbone.Model, và sau đó bắt đầu thực hiện sự kiện ma thuật sôi sục.
Domenic

74

Tôi đã tạo backbone-deep-model cho việc này - chỉ cần mở rộng Backbone.DeepModel thay vì Backbone.Model và sau đó bạn có thể sử dụng các đường dẫn để lấy / đặt các thuộc tính mô hình lồng nhau. Nó cũng duy trì các sự kiện thay đổi.

model.bind('change:user.name.first', function(){...});
model.set({'user.name.first': 'Eric'});
model.get('user.name.first'); //Eric

1
Có, nó có, nếu bạn nhìn vào API, có một ví dụ như//You can use index notation to fetch from arrays console.log(model.get('otherSpies.0.name')) //'Lana'
tawheed

Hoạt động tuyệt vời! Nhưng dòng 2 trong ví dụ của bạn có yêu cầu dấu hai chấm thay vì dấu phẩy không?
mariachi

16

Giải pháp của Domenic sẽ hoạt động tuy nhiên mỗi MyModel mới sẽ trỏ đến cùng một phiên bản của Obj. Để tránh điều này, MyModel sẽ giống như sau:

var MyModel = Backbone.Model.extend({
  initialize: function() {
     myDefaults = {
       obj1: new Obj()
     } 
     this.set(myDefaults);
  }
});

Xem câu trả lời của c3rin @ https://stackoverflow.com/a/6364480/1072653 để được giải thích đầy đủ.


1
Đối với những độc giả trong tương lai, câu trả lời của tôi đã được cập nhật để kết hợp với câu trả lời hay nhất của Rusty.
Domenic

2
Người hỏi nên đánh dấu đây là câu trả lời được chấp nhận. Domenic's là một khởi đầu tuyệt vời, nhưng điều này giải quyết được một vấn đề với nó.
Jon Raasch

5

Tôi sử dụng cách tiếp cận này.

Nếu bạn có một mô hình Backbone như thế này:

var nestedAttrModel = new Backbone.Model({
    a: {b: 1, c: 2}
});

Bạn có thể đặt thuộc tính "ab" bằng:

var _a = _.omit(nestedAttrModel.get('a')); // from underscore.js
_a.b = 3;
nestedAttrModel.set('a', _a);

Bây giờ mô hình của bạn sẽ có các thuộc tính như:

{a: {b: 3, c: 2}}

với sự kiện "thay đổi" được kích hoạt.


1
Bạn có chắc về điều này? điều này không làm việc cho tôi. meta2= m.get('x'); meta2.id=110; m.set('x', meta2). Điều này không kích hoạt bất kỳ sự kiện thay đổi nào đối với tôi :(
HungryCoder

1
Tôi thấy nó hoạt động khi tôi sao chép thuộc tính như _.clone(m.get('x')). cảm ơn
HungryCoder

Cảm ơn @HungryCoder, nó cũng đã hoạt động với tôi khi được nhân bản. Backbone phải so sánh đối tượng bạn đang settingcó với đối tượng bạn đang gettingở tại thời điểm đã định. Vì vậy, nếu bạn không nhân bản hai đối tượng, thì hai đối tượng được so sánh là hoàn toàn giống nhau tại thời điểm thiết lập.
Derek Dahmer

Hãy nhớ rằng các đối tượng được truyền bằng tham chiếu và có thể thay đổi, không giống như các nguyên thủy chuỗi và số. Các phương thức thiết lập và phương thức khởi tạo của Backbone cố gắng sao chép nông của bất kỳ tham chiếu đối tượng nào được truyền dưới dạng đối số. Mọi tham chiếu đến các đối tượng khác trong thuộc tính của đối tượng đó không được sao chép. Khi bạn đặt nó và truy xuất nó, tham chiếu sẽ giống nhau, có nghĩa là bạn có thể thay đổi mô hình mà không cần kích hoạt thay đổi.
niall.campbell

3

Có một giải pháp mà chưa ai nghĩ đến là rất nhiều để sử dụng. Bạn thực sự không thể đặt các thuộc tính lồng nhau một cách trực tiếp, trừ khi bạn sử dụng thư viện của bên thứ ba mà bạn có thể không muốn. Tuy nhiên, những gì bạn có thể làm là tạo một bản sao của từ điển gốc, đặt thuộc tính lồng nhau ở đó và hơn là đặt toàn bộ từ điển đó. Miếng bánh.

//How model.obj1 looks like
obj1: {
    myAttribute1: false,
    myAttribute2: true,
    anotherNestedDict: {
        myAttribute3: false
    }
}

//Make a clone of it
var cloneOfObject1 = JSON.parse(JSON.stringify(this.model.get('obj1')));

//Let's day we want to change myAttribute1 to false and myAttribute3 to true
cloneOfObject1.myAttribute2 = false;
cloneOfObject1.anotherNestedDict.myAttribute3 = true;

//And now we set the whole dictionary
this.model.set('obj1', cloneOfObject1);

//Job done, happy birthday

2

Tôi đã gặp vấn đề tương tự @pagewil và @Benno với giải pháp của @ Domenic. Thay vào đó, câu trả lời của tôi là viết một lớp con đơn giản của Backbone.Model để khắc phục sự cố.

// Special model implementation that allows you to easily nest Backbone models as properties.
Backbone.NestedModel = Backbone.Model.extend({
    // Define Backbone models that are present in properties
    // Expected Format:
    // [{key: 'courses', model: Course}]
    models: [],

    set: function(key, value, options) {
        var attrs, attr, val;

        if (_.isObject(key) || key == null) {
            attrs = key;
            options = value;
        } else {
            attrs = {};
            attrs[key] = value;
        }

        _.each(this.models, function(item){
            if (_.isObject(attrs[item.key])) {
                attrs[item.key] = new item.model(attrs[item.key]);
            }
        },this);

        return Backbone.Model.prototype.set.call(this, attrs, options);
    }
});

var Obj = Backbone.Model.extend({
    defaults: {
        myAttribute1: false,
        myAttribute2: true
    }
});

var MyModel = Backbone.NestedModel.extend({
    defaults: {
        obj1: new Obj()
    },

    models: [{key: 'obj1', model: Obj}]
});

Những gì NestedModel làm cho bạn là cho phép những thứ này hoạt động (đó là điều xảy ra khi myModel được thiết lập thông qua dữ liệu JSON):

var myModel = new MyModel();
myModel.set({ obj1: { myAttribute1: 'abc', myAttribute2: 'xyz' } });
myModel.set('obj1', { myAttribute1: 123, myAttribute2: 456 });

Sẽ rất dễ dàng để tạo danh sách mô hình tự động khi khởi tạo, nhưng giải pháp này đủ tốt đối với tôi.


2

Giải pháp do Domenic đề xuất có một số hạn chế. Giả sử bạn muốn nghe sự kiện 'thay đổi'. Trong trường hợp đó, phương thức 'khởi tạo' sẽ không được kích hoạt và giá trị tùy chỉnh của bạn cho thuộc tính sẽ được thay thế bằng đối tượng json từ máy chủ. Trong dự án của tôi, tôi phải đối mặt với vấn đề này. Giải pháp của tôi để ghi đè phương thức 'set' của Model:

set: function(key, val, options) {
    if (typeof key === 'object') {
        var attrs = key;
        attrs.content = new module.BaseItem(attrs.content || {});
        attrs.children = new module.MenuItems(attrs.children || []);
    }

    return Backbone.Model.prototype.set.call(this, key, val, options);
}, 

0

Mặc dù trong một số trường hợp sử dụng các mô hình Backbone thay vì các thuộc tính Đối tượng lồng nhau có ý nghĩa như Domenic đã đề cập, trong những trường hợp đơn giản hơn, bạn có thể tạo một hàm setter trong mô hình:

var MyModel = Backbone.Model.extend({
    defaults: {
        obj1 : {
            "myAttribute1" : false,
            "myAttribute2" : true,
        }
    },
    setObj1Attribute: function(name, value) {
        var obj1 = this.get('obj1');
        obj1[name] = value;
        this.set('obj1', obj1);
    }
})

0

Nếu bạn tương tác với phần phụ trợ, yêu cầu đối tượng có cấu trúc lồng nhau. Nhưng với đường trục dễ làm việc hơn với cấu trúc tuyến tính.

backbone.linear có thể giúp bạn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.