Backbone.js获取并设置嵌套对象属性


105

我有一个关于Backbone.js的getset函数的简单问题。

1)使用下面的代码,如何直接“获取”或“设置” obj1.myAttribute1?

另一个问题:

2)在模型中,除了默认对象之外,我还可以/应该在哪里声明模型的其他属性,以便可以通过Backbone的get和set方法访问它们?

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?
    }
});

我知道我可以做:

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

但这是好习惯吗?


3
虽然它不是一个问题的答案:只要指定一个对象(任何通过引用传递)defaults(OBJ1在这种情况下),即相同的对象将会在模型的所有实例共享。当前的做法是将defaults函数定义为返回要用作默认值的对象的函数。骨架js.org/#Model-defaults(请参见斜体字注释)
Jonathan F

1
@JonathanF评论不是为了回答,所以您永远不需要声明:)
TJ

Answers:


144

虽然this.model.get("obj1").myAttribute1很好,但还是有点问题,因为那样的话,您可能会想为set做相同类型的事情,即

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

但是,如果这样做,您将无法获得Backbone模型的优势myAttribute1,例如更改事件或验证。

更好的解决方案是永远不要在模型中嵌套POJSO(“普通的旧JavaScript对象”),而嵌套自定义模型类。所以看起来像这样:

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

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

那么访问代码将是

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

但更重要的是,设置代码为

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

这将触发适当的更改事件等。这里的工作示例:http : //jsfiddle.net/g3U7j/


24
对于这个答案,我要补充一点的建议是,该解决方案会影响广泛的违反Demeter的法律。我会考虑添加一些方便的方法来隐藏对嵌套对象的导航。基本上,您的调用者不需要知道模型的内部结构;毕竟,它可能会发生变化,调用方应该更明智。
Bill Eisenhauer

7
不能让它为我工作。引发错误:Uncaught TypeError: Object #<Object> has no method 'set'
wilsonpage 2011年

1
@ ChristianNunciato,pagewil,Benno:您似乎错过了这篇文章的要点,即将Backbone模型嵌套在Backbone模型中。不要在Backbone模型中嵌套普通对象。这里的工作示例:jsfiddle.net/g3U7j
Domenic,

1
我没有检查ribs.js代码,但是从我的测试来看,如果您有一个嵌套的自定义模型并使用set()更改其属性,则其父模型本身不会触发“更改”事件;我必须自己触发该事件。我真的应该只检查代码,但这也是您的理解吗?
汤姆

2
@tom是正确的。当模型的属性是的实例Backbone.Model,然后开始进行神奇的事件冒泡时,Backbone并不特殊。
Domenic 2013年

74

我为此创建了骨架深层模型 -只需扩展Backbone.DeepModel而不是Backbone.Model,然后您就可以使用路径来获取/设置嵌套模型属性。它也维护变更事件。

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

1
是的,确实如此,如果您查看API,就会看到一个类似的示例//You can use index notation to fetch from arrays console.log(model.get('otherSpies.0.name')) //'Lana'
tawheed 2013年

很棒!但是,您的示例中的第2行是否需要用冒号而不是逗号?
mariachi

16

Domenic的解决方案将起作用,但是每个新的MyModel都将指向相同的Obj实例。为了避免这种情况,MyModel应该如下所示:

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

有关完整说明,请参见c3rin的答案@ https://stackoverflow.com/a/6364480/1072653


1
对于将来的读者,我的答案已更新为包含Rusty的最佳答案。
多梅尼克

2
询问者应将此标记为已接受的答案。Domenic是一个很好的开始,但这解决了一个问题。
乔恩·拉施(Jon Raasch)2012年

5

我使用这种方法。

如果您有这样的骨干网模型:

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

您可以通过以下方式设置属性“ ab”:

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

现在,您的模型将具有以下属性:

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

触发“更改”事件。


1
你确定吗?这对我不起作用。meta2= m.get('x'); meta2.id=110; m.set('x', meta2)。这不会为我触发任何更改事件:(
HungryCoder

1
当我像克隆属性时,我看到它有效_.clone(m.get('x'))。谢谢
HungryCoder

谢谢@HungryCoder,它在克隆时也对我有用。骨干必须将您的对象settinggetting设置时间的对象进行比较。因此,如果您不克隆这两个对象,则被比较的两个对象在设置的时间完全相同。
德里克·达默

请记住,对象是通过引用传递的并且是可变的,这与字符串和数字基元不同。Backbone的set和Constructor方法尝试对作为参数传递的任何对象引用进行浅表克隆。不会克隆该对象属性中对其他对象的任何引用。设置和检索参考时,参考是相同的,这意味着您可以对模型进行变异而无需触发更改。
niall.campbell,

3

目前尚无人想到的一种解决方案可供使用。确实,您不能直接设置嵌套属性,除非您使用了可能不想要的第三方库。但是,您可以做的是克隆原始字典,在其中设置嵌套属性,然后设置整个字典。小菜一碟。

//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

我在@Domenic的解决方案中遇到了@pagewil和@Benno相同的问题。我的答案是改写一个简单的Backbone.Model子类来解决问题。

// 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}]
});

NestedModel为您所做的就是允许这些工作(当通过JSON数据设置myModel时会发生这种情况):

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

在初始化时自动生成模型列表会很容易,但是这个解决方案对我来说已经足够了。


2

Domenic提出的解决方案有一些缺点。假设您想收听“更改”事件。在这种情况下,不会触发“ initialize”方法,并且您的属性自定义值将被服务器中的json对象替换。在我的项目中,我遇到了这个问题。我的解决方案来覆盖模型的“设置”方法:

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

在某些情况下,如Domenic所述,使用Backbone模型代替嵌套的Object属性是有意义的,但在更简单的情况下,您可以在模型中创建setter函数:

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

如果与后端交互,则需要具有嵌套结构的对象。但是有了骨干,线性结构就更容易工作了。

bone.linear可以为您提供帮助。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.