Backbone.js 视图的默认值是什么?

19
我正在使用 Backbone.js,想知道是否可以像设置模型默认值一样设置默认值?
10个回答

30

你可以在initialize函数中设置默认值。

defaults: {
  display: 'list'
},

initialize: function() {
  this.options = _.extend({}, this.defaults, this.options);
}

这将适用于普通选项,但不会覆盖任何特殊选项(也就是Backbone存储在视图对象上的选项 - ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName'])。

查看一个工作演示:http://jsfiddle.net/dira/7MmQE/1/


4
#documentcloud上的好心人指出了问题。this.defaults来自原型,因此在两个实例中是相同的对象。使用this.defaults和this.options扩展一个新对象可以解决这个问题。我已经适当地编辑了@dira的答案。 - xn.
8
使用 _.defaults() 是一个更加简洁的解决方案。 - xn.
2
我遇到了相同的问题,但在浏览了一段时间后,我发现了这个链接(https://github.com/documentcloud/underscore/issues/106)。this.options = _.extend({}, this.defaults, this.options)。 - Gaurang Jadia
当您从此视图继承并希望提供不同的默认值时,应该使用this.defaults2吗?嗯... - ajbeaven
自Backbone 1.1.0版本起,this.options不再自动填充。解决此问题的最简单方法是使用backbone.viewOptions插件 - Brave Dave
显示剩余2条评论

25

对于 Backbone 1.1 或更新版本

方法A: 在 initialize 中使用 OptionsInLiteral 和 _.defaults

var MyView = Backbone.View.extend({
  options: {
    enabled: true,
    align:   "left"
  },
  initialize: function (options) {
    #be sure to do the '|| {}' here so 'new MyView()' works
    this.options = _.defaults(options || {}, this.options);
  }
});

方法二:使用viewOptions插件(或类似插件)

https://github.com/rotundasoftware/backbone.viewOptions

感谢@BraveDave在评论中指出这一点。

对于版本在1.1之前的Backbone(历史参考,供您了解)

这是关于backbone问题的讨论,在那里看起来核心团队最有可能完全取消this.options_configure中的逻辑。

使用options属性并始终使用this.options

关于此问题存在很多混淆,甚至有一个得到高票并被接受但不正确的答案。希望这个答案可以演示一个真正正确的解决方案,并指出所有其他候选答案中的缺陷。

为了与Backbone.View父类协同工作,您应该在传递给Backbone.View.extend的对象文字中包含一个options属性。

var OptionsInLiteral = Backbone.View.extend({
  options: {flavor: "vanilla"},
  initialize: function (options) {
    console.log("OptionsInLiteral.initialize first argument", options);
    console.log("OptionsInLiteral.initialize this.options", this.options);
  }
});

以下是一些示例以及它们在控制台中记录的内容。

new OptionsInLiteral();
    //OptionsInLiteral.initialize first argument undefined
    //OptionsInLiteral.initialize this.options Object {flavor: "vanilla"}
new OptionsInLiteral({flavor: "chocolate"});
    //OptionsInLiteral.initialize first argument Object {flavor: "chocolate"}
    //OptionsInLiteral.initialize this.options Object {flavor: "chocolate"}
new OptionsInLiteral({flavor: "strawberry", sprinkles: true});
    //OptionsInLiteral.initialize first argument Object {flavor: "strawberry", sprinkles: true}
    //OptionsInLiteral.initialize this.options Object {flavor: "strawberry", sprinkles: true}

这将正确地利用Backbone.View._configure,在Backbone 1.0.0中它看起来是这样的:

_configure: function(options) {
  if (this.options) options = _.extend({}, _.result(this, 'options'), options);
  _.extend(this, _.pick(options, viewOptions));
  this.options = options;
},

这意味着:

  • 如果你的视图对象文字包含options_configure将正确地将它们作为默认值处理,在构造函数中用传递的属性覆盖它们,并将最终结果对象设置为this.options。太好了,这就是我们想要的。
  • 即使没有参数调用视图构造函数也可以这样操作。太好了,也是我们想要的。
  • 因为这里使用了_.result,所以options属性可以是Objectfunction,如果是函数,它将被调用,并且返回值将被使用。

这也是可接受的,并允许每个实例具有唯一的默认值。

var OptionsFunctionInLiteral = Backbone.View.extend({
  options: function () {
    return {
      flavor: "vanilla",
      created: Date(),
      collection: new Backbone.Collection()
    };
  },
  initialize: function (options) {
    console.log("OptionsFunctionInLiteral.initialize first argument", options);
    console.log("OptionsFunctionInLiteral.initialize this.options", this.options);
  }
});

以下是一些示例以及它们在控制台中记录的内容。

new OptionsFunctionInLiteral();
    //OptionsFunctionInLiteral.initialize first argument undefined
    //OptionsFunctionInLiteral.initialize this.options Object {flavor: "vanilla", created: "Wed Jun 19 2013 16:20:16 GMT-0600 (MDT)", collection: Backbone.Collection}
new OptionsFunctionInLiteral({flavor: "chocolate"});
    //OptionsFunctionInLiteral.initialize first argument Object {flavor: "chocolate"}
    //OptionsFunctionInLiteral.initialize this.options Object {flavor: "chocolate", created: "Wed Jun 19 2013 16:21:17 GMT-0600 (MDT)", collection: Backbone.Collection}
new OptionsFunctionInLiteral({flavor: "strawberry", sprinkles: true});
    //OptionsFunctionInLiteral.initialize first argument Object {flavor: "strawberry", sprinkles: true}
    //OptionsFunctionInLiteral.initialize this.options Object {flavor: "strawberry", created: "Wed Jun 19 2013 16:22:26 GMT-0600 (MDT)", collection: Backbone.Collection, sprinkles: true}

为什么你应该始终使用this.options

所以,以上内容是很好的,但有一个限制,如果没有参数调用视图的构造函数,在initialize函数中,this.options将存在并且是正确的,但是传递给initialize函数的纯粹options参数将是undefined

initialize: function (options) {
  console.log(options.flavor); //BUG! options is undefined. Uncaught exeption. :-(
  console.log(this.options); //correct
}

因此,当我定义我的初始化时,我甚至不将函数的options参数指定为提醒自己不要使用它。一般来说,您要忽略initializeoptions参数,因为它不包含默认值。

有缺陷的答案:_.extend(this.defaults, this.options)

这个答案存在错误,因为每次实例化一个实例时,它无意中修改了所有未来实例的默认值。

var DefaultsExtendView = Backbone.View.extend({
  defaults: {flavor: "vanilla"},
  initialize: function (options) {
    console.log("initialize 1st argument", options);
    this.options = _.extend(this.defaults, this.options);
    console.log("initialize this.options", this.options);
  }
});

new DefaultsExtendView(); //OK
new DefaultsExtendView({flavor: "chocolate"}); //OK
new DefaultsExtendView(); //BUG! You get chocolate instead of vanilla

有缺陷的答案:if (options.foo)

var myView = Backbone.View.extend({
    foo: "default_value",

    initialize: function(options) {
        if(options.foo) {
            foo = options.foo;
        }
    }
});

new myView(); //BUG! options is undefined, uncaught exception
//TypeError: Cannot read property 'foo' of undefined

注意选项对象和实例特定默认值

这个问题的一个答案建议使用以下内容:

var DefaultsView = Backbone.View.extend({
  defaults: {
    collection: new Backbone.Collection()
  },
  initialize: function () {
    _.defaults(this.options, this.defaults);

这几乎肯定不是你想要的结果,也是一个错误。如果你创建了十个视图,它们都将共享同一个Backbone.Collection实例,因为在定义视图子类时只会创建一个实例。当您向view9的集合中添加模型时,它一定会出现在所有视图中,这肯定会让您感到困惑。更可能的是,您希望每个视图实例都有一个不同的新集合实例,并且要做到这一点,您需要使options成为一个函数,就像我的上面的示例。

正确方法的总结

  1. 使用 options: {...}options: function () {...}
  2. 声明您的initialize没有任何参数
  3. 将默认选项作为 this.options 访问

示例样板文件

var MyView = Backbone.View.extend({
  options: {flavor: "vanilla"},
  initialize: function () { //note no declared arguments
      //use this.options here as needed and all is well
  }
});

工作中的jsfiddle

http://jsfiddle.net/DUc25/


this.options 在 Backbone 1.1.0 中已被删除,这种方法将不再起作用。只需使用 backbone.viewOptions 插件 即可。 - Brave Dave
谢谢,@BraveDave。我更新了答案。如果您在代码片段中发现任何问题,请告诉我。 - Peter Lyons

12

针对Backbone 1.1或更新版本

方法A: 在初始化函数中使用OptionsInLiteral和_.defaults

var MyView = Backbone.View.extend({
  options: {
    enabled: true,
    align:   "left"
  },
  initialize: function (options) {
    #be sure to do the '|| {}' here so 'new MyView()' works
    this.options = _.defaults(options || {}, this.options);
  }
});

方案B:使用viewOptions插件(或类似插件)

backbone.viewOptions

感谢@BraveDave在评论中指出这一点。

对于版本1.1之前的Backbone(历史参考,供参考)

这里是关于似乎核心团队最有可能摆脱this.options_configure逻辑的Backbone问题。

使用options属性并始终使用this.options

这个问题引起了很多混淆,即使是一个高度赞同但错误的答案也被接受。希望这个答案能够演示真正正确的解决方案,并指出其他所有候选答案中存在的错误。

为了与Backbone.View父类协作,您应该在传递给Backbone.View.extend的对象字面量中包含一个options属性。

var OptionsInLiteral = Backbone.View.extend({
  options: {flavor: "vanilla"},
  initialize: function (options) {
    console.log("OptionsInLiteral.initialize first argument", options);
    console.log("OptionsInLiteral.initialize this.options", this.options);
  }
});

这里有一些示例以及它们在控制台中记录的内容。

new OptionsInLiteral();
    //OptionsInLiteral.initialize first argument undefined
    //OptionsInLiteral.initialize this.options Object {flavor: "vanilla"}
new OptionsInLiteral({flavor: "chocolate"});
    //OptionsInLiteral.initialize first argument Object {flavor: "chocolate"}
    //OptionsInLiteral.initialize this.options Object {flavor: "chocolate"}
new OptionsInLiteral({flavor: "strawberry", sprinkles: true});
    //OptionsInLiteral.initialize first argument Object {flavor: "strawberry", sprinkles: true}
    //OptionsInLiteral.initialize this.options Object {flavor: "strawberry", sprinkles: true}

这将正确利用Backbone.View._configure,在 Backbone 1.0.0 中它的样子是这样的:

_configure: function(options) {
  if (this.options) options = _.extend({}, _.result(this, 'options'), options);
  _.extend(this, _.pick(options, viewOptions));
  this.options = options;
},

这意味着:

  • 如果您的视图对象文字包含 options_configure 将适当地将它们视为默认值,用传递到构造函数中的属性覆盖它们,并将最终结果对象设置为 this.options。太好了,这正是我们想要的。
  • 即使在没有参数的情况下调用视图构造函数也可以正常工作。太好了,这也是我们想要的。
  • 由于这里使用了 _.result,因此 options 属性可以是一个 Object 或一个 function,如果它是一个函数,它将被调用并使用返回值。

这也是可接受的,并允许每个实例具有唯一的默认值。

var OptionsFunctionInLiteral = Backbone.View.extend({
  options: function () {
    return {
      flavor: "vanilla",
      created: Date(),
      collection: new Backbone.Collection()
    };
  },
  initialize: function (options) {
    console.log("OptionsFunctionInLiteral.initialize first argument", options);
    console.log("OptionsFunctionInLiteral.initialize this.options", this.options);
  }
});

这里是一些示例以及它们在控制台上记录的内容。

new OptionsFunctionInLiteral();
    //OptionsFunctionInLiteral.initialize first argument undefined
    //OptionsFunctionInLiteral.initialize this.options Object {flavor: "vanilla", created: "Wed Jun 19 2013 16:20:16 GMT-0600 (MDT)", collection: Backbone.Collection}
new OptionsFunctionInLiteral({flavor: "chocolate"});
    //OptionsFunctionInLiteral.initialize first argument Object {flavor: "chocolate"}
    //OptionsFunctionInLiteral.initialize this.options Object {flavor: "chocolate", created: "Wed Jun 19 2013 16:21:17 GMT-0600 (MDT)", collection: Backbone.Collection}
new OptionsFunctionInLiteral({flavor: "strawberry", sprinkles: true});
    //OptionsFunctionInLiteral.initialize first argument Object {flavor: "strawberry", sprinkles: true}
    //OptionsFunctionInLiteral.initialize this.options Object {flavor: "strawberry", created: "Wed Jun 19 2013 16:22:26 GMT-0600 (MDT)", collection: Backbone.Collection, sprinkles: true}

为什么你应该始终使用this.options

因此,上述内容很好,但有一个要注意的地方,即如果没有参数调用视图的构造函数,则在initialize函数中,this.options将存在并且正确,但是传递给initialize函数的 options参数将是undefined

initialize: function (options) {
  console.log(options.flavor); //BUG! options is undefined. Uncaught exeption. :-(
  console.log(this.options); //correct
}
因此,当我定义我的初始化时,我甚至不把函数的options参数指定为提醒不要使用它。一般情况下,您希望忽略initializeoptions参数,因为它不包含默认值。






错误答案:_.extend(this.defaults, this.options)

这个答案有一个错误,它无意中在每次实例化时无意中修改了所有未来实例的默认值。

var DefaultsExtendView = Backbone.View.extend({
  defaults: {flavor: "vanilla"},
  initialize: function (options) {
    console.log("initialize 1st argument", options);
    this.options = _.extend(this.defaults, this.options);
    console.log("initialize this.options", this.options);
  }
});

new DefaultsExtendView(); //OK
new DefaultsExtendView({flavor: "chocolate"}); //OK
new DefaultsExtendView(); //BUG! You get chocolate instead of vanilla

有缺陷的答案:if (options.foo)

var myView = Backbone.View.extend({
    foo: "default_value",

    initialize: function(options) {
        if(options.foo) {
            foo = options.foo;
        }
    }
});

new myView(); //BUG! options is undefined, uncaught exception
//TypeError: Cannot read property 'foo' of undefined

小心选项对象和实例特定默认值

这个问题的一个答案建议如下:

var DefaultsView = Backbone.View.extend({
  defaults: {
    collection: new Backbone.Collection()
  },
  initialize: function () {
    _.defaults(this.options, this.defaults);

这几乎肯定不是你想要的结果,而且是一个错误。如果你创建了10个视图,它们将共享同一个Backbone.Collection实例,因为当视图子类定义时只会创建1个实例。当你向view9的集合添加一个模型并在所有视图中显示出来时,这肯定会让你感到困惑。更有可能的情况是,你希望每个视图实例都有一个不同的新集合实例,并且要做到这一点,需要将options设置为函数,就像我上面的示例那样。

正确方法的摘要

  1. 使用options: {...}options: function () {...}
  2. 声明你的initialize没有任何参数
  3. 将你的默认选项作为this.options访问

示例骨架代码

var MyView = Backbone.View.extend({
  options: {flavor: "vanilla"},
  initialize: function () { //note no declared arguments
      //use this.options here as needed and all is well
  }
});

可工作的JSFiddle

http://jsfiddle.net/DUc25/


2
试图进行鸭子打补丁。 来自Backbone源代码:
var View = Backbone.View = function(options) {
    this.cid = _.uniqueId('view');
    this._configure(options || {});
    this._ensureElement();
    this.initialize.apply(this, arguments);
    this.delegateEvents();
};

我们将重写_configure:
// Save _configure method
var _oldConfigure = Backbone.View.prototype._configure;

Backbone.View.prototype._configure = function(options){
    _.defaults(options, this.defaults); 
    _oldConfigure.call(this, options);
};

现在Backbone.View与Backbone.model一样,关于defaults的处理方式也相同,你甚至不需要在构造函数/初始化方法中做任何事情。

2
var DefaultsView = Backbone.View.extend({
  defaults: {
    collection: new Backbone.Collection()
  },
  initialize: function () {
    _.defaults(this.options, this.defaults);
    // Ensures keys with special meaning (model, collection, id, className, etc.), are attached directly to the view
    Backbone.View.prototype._configure.apply(this, arguments);
  },
  render: function () {
    console.log(this.collection);
  }
});

var view = new DefaultsView();

view.render();

1

根据 Backbone.View 的解释,它说:

constructor / initializenew View([options])

创建新视图时,传递的选项将作为 this.options 附加到视图上,以供将来参考。如果传递了几个特殊选项,它们将直接附加到视图上:modelcollectionelidclassNametagNameattributes。如果视图定义了 initialize 函数,则在首次创建视图时将调用该函数。如果您想创建一个引用已经存在于 DOM 中的元素的视图,请将该元素作为选项传递:new View({el: existingElement})。

我想知道为什么默认值在视图中没有像在 Model 和 Collection 中那样使用。


请添加引用链接。 - user2954463

0

如果我理解问题正确,您可以通过以下方式设置默认值:

scope.MyView = Backbone.View.extend({
    x: 'x',
})

var obj = new scope.MyView({
    y: 'y',
});

console.log( this.options );
// output: { x: 'x', y:'y' }

问题在于,View 构造函数文档中所述的行为并没有完全反映出来。这是因为复合对象没有被完全复制。这是因为 _configure 使用了 underscore.js 的 _.extend,而它不是递归的。
这意味着,如果你做了这样的事情:
scope.MyView = Backbone.View.extend({
    x: {
        a: 'a',
    }
})

var obj = new scope.MyView({
    x: {
        b: 'b',
    }
});

console.log( this.options );
// output: { x: { b:'b' } }

不按预期工作。如果您将属性传递给视图并具有默认属性,则可能会发生这种情况。您传递的属性将被覆盖。


0
使用the backbone.viewOptions插件,它正好做你要找的东西:
// Add the view options plugin functionality to all our views.
Backbone.ViewOptions.add( Backbone.View.prototype );

MyView = Backbone.View.extend( {
    options : [
        "type", // normal options are declared like so
        { "label" : "OK" } // options with defaults are declared like so
     ],

    initialize : function( options ) {
        this.setOptions( options ); // attaches declared options to the view

        console.log( this.label ); // outputs "OK"
        console.log( this.type ); // outputs "button"
    }
} );

new MyView( { type : "button" } );

0

这个堆栈对我来说有点误导人。Peter Lyons的答案似乎是当前正确的,尽管没有得到最多的投票。

从Backbone文档中可以看到...

创建新视图时,您传递的选项(在合并到视图上已经存在的任何默认选项之后)将作为this.options附加到视图上,以供将来参考。

http://backbonejs.org/#View-constructor

我通过在类定义中实现一个选项,成功地使其工作。

MyScope.MyView = Backbone.View.extend({
    options: {
        default_prop: 'value1',
        another_default: 'value2'
    }
}

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接