【问题标题】:Backbone.js Handling of Attributes That Are ArraysBackbone.js 处理作为数组的属性
【发布时间】:2011-06-22 01:06:34
【问题描述】:

我真的很喜欢 Backbone,但我在做看似简单的事情时却遇到了困难。感谢您对以下示例的任何帮助。

我有一个模型 Criteria,我想用它来存储 UI 中某些项目的状态。有几个简单的属性,一个属性是一组 ID,用于存储用户在 UI 中选择的标签的 ID。

所以,我创建了一个新实例。我在标签数组中添加了一些项目。然后,我想重新开始,创建一个新实例,分配给同一个变量。但是,我的 tags 数组继续保存我作为第一个 Criteria 实例添加到其中的信息。

我已经记录了下面的测试用例。

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Test</title>
    <script src="Scripts/Libraries/jquery-1.6.1.js" type="text/javascript"></script>
    <script src="Scripts/Libraries/underscore.js" type="text/javascript"></script>
    <script src="Scripts/Libraries/backbone.js" type="text/javascript"></script>

    <script language="javascript" type="text/javascript">

        $(function () {

            // Simple model to hold some state about my UI.
            var Criteria = Backbone.Model.extend({

                defaults: {
                    "status": "Normal",
                    "priority": "Normal",
                    "tags": new Array()
                }

            });

            // Create new criteria.
            window.criteria = new Criteria();

            // The length of the tags array should be 0. PASSES
            console.log("Expect 0: Actual " + window.criteria.get("tags").length);

            // Add a tag id to the tags array.
            window.criteria.get("tags").push(5); // Tag with ID of 5.

            // The length of the tags array should be 1. PASSES
            console.log("Expect 1: Actual " + window.criteria.get("tags").length);

            // Create a new instance of criteria.
            window.criteria = new Criteria();

            // The length of the tags array should be 0. FAILS
            // CONFUSED. I thought this is now a new instance with a new set of attributes.
            // Why does the tags collection still have an item in it.
            console.log("Expect 0: Actual " + window.criteria.get("tags").length);

            // OK. So, I will call the clear method on the model. This is supposed to remove all attributes
            // from the model.
            // Then, I will create it again.
            window.criteria.clear();
            window.criteria = new Criteria();

            // The length of the tags array should be 0. FAILS. Still 1.
            console.log("Expect 0: Actual " + window.criteria.get("tags").length);

            // ARGH!
            console.log("HELP!");

        });

    </script>

</head>
<body>
    <h1>Test</h1>
    <p>Backbone test page.</p>
</body>
</html>

我是不是有点离题了?我是否试图将 Backbone 用于非预期的事情?还是我在 javascript OO 编程中遗漏了一些更通用的东西?

附:我最初使用了一个 Backbone 的标签集合,但它提出了一组完全不同的问题,这些问题与在多个集合中引用标签模型有关,以及当从任何集合中删除项目时,Backbone 的 remove 方法如何取消设置“集合”引用。改天,另一个问题。

【问题讨论】:

  • 嗨,凯文,也许您可​​以将您接受的答案更改为下面的答案?这是更清晰和推荐的方法。无论如何,公认的答案只是对同一件事的夸大其词。谢谢。

标签: javascript backbone.js


【解决方案1】:

“defaults”也可以是一个函数。

var Criteria = Backbone.Model.extend({
    defaults: function () {
        return {
            "status": "Normal",
            "priority": "Normal",
            "tags": new Array()
        }
    }
});

这将在实例化新条件时创建一个新数组。 见:http://backbonejs.org/#Model-defaults

【讨论】:

  • 退货的目的是什么?
  • 关键是“defaults”属性现在是一个函数而不是一个对象。 return 表示从函数返回并用作“默认值”的值的对象。正如其他人所解释的,如果“defaults”是一个对象,则在创建新模型时,“defaults”中的每个属性都会被复制到对象的属性中。当属性是字符串或数字时,这将按预期工作,但对于对象和数组,此副本是通过引用完成的。对于每个实例都有自己的默认属性副本,您应该使用一个函数。
  • 我更喜欢这种语法,而不是所选答案中的语法
  • 请注意,您可以只使用 [] 而不是 "new Array()"
  • 这是你应该这样做的方式。 “请记住,在 JavaScript 中,对象是通过引用传递的,因此如果您将对象作为默认值包含在内,它将在所有实例之间共享。相反,将默认值定义为函数。” - backbonejs.org/#Model-defaults
【解决方案2】:

Thom Blake 关于为什么要为数组保留相同的值是正确的。解决此问题的一种方法是在初始化程序中设置默认值

        var Criteria = Backbone.Model.extend({

            defaults: {
                "status": "Normal",
                "priority": "Normal"
            },

            initialize: function(){
              if( !this.get('tags') ){ 
                this.set({tags: new Array()});
              }
            }

        });

【讨论】:

  • 谢谢。我最终采用了这种方法,现在它非常有意义。所以,只是为了确保原始值(状态、优先级)不需要像标签数组一样被重置?对吗?
  • 有这样的代码,但我将 {silent: true} 传递给 this.set(..., {silent: true}) 调用,然后当我通过更改的属性时,我也这样做了看看我在初始化程序中用 this.set(... {silent: true}) 改变的那些不是我想要的。
【解决方案3】:

当您在“默认值”下定义“标签”时,您会创建一个新数组并将其设置为该类的默认值。然后,当您创建一个新实例时,它具有相同的 Array 引用,其中仍然包含您推入其中的内容。

与其为标签设置默认值,不如在第一次使用之前将其设置为[]

window.criteria = new Criteria()
window.criteria.set({'tags', []})  //you can use new Array() if you want
window.criteria.get('tags').push(5)

window.criteria = new Criteria()
console.log(window.criteria.get('tags'))   //should be undefined
window.criteria.set({'tags', []})

【讨论】:

  • 我最近在将 js Date 对象作为属性存储在 Backbone.Model 类型类中时遇到了同样的问题。我没有创建新的日期对象,只是更改了一个引用。因此,就骨干而言,日期从未改变,事件从未触发。规则是骨干始终将属性视为不可变的值。如果您需要更改属性,请克隆一个新值,该值将创建一个新引用并更改新引用。
  • 我继续按照下面的初始化方法。很好的解释。谢谢!
【解决方案4】:

需要明确的是,Maksym H. 提供的最后一个选项不会解决问题。假设所有设置的值都是不可变的,则提供了 defaults 属性。然而,数组是可变的,这意味着它的值可以更改(例如 tags[0] = "hello" 可以通过 tags[0] = "hi there" 进行更改)。

通过使用 btford 的答案,您将强制在模型的每个新实例上创建任何可变对象/属性的新实例,因此它永远不会共享,因为该对象是使用函数作用域变量创建的。

同样,Derick Bailey 的回答是正确的,它只是使用了 initialize 方法而不是 defaults 方法。

【讨论】:

    猜你喜欢
    • 2017-12-16
    • 1970-01-01
    • 2020-08-15
    • 2011-04-25
    • 2022-01-08
    • 1970-01-01
    • 1970-01-01
    • 2016-04-20
    相关资源
    最近更新 更多