不Backbone.Models this.get()整个数组或点复制到内存中的同一阵列 [英] does Backbone.Models this.get() copy an entire array or point to the same array in memory

查看:156
本文介绍了不Backbone.Models this.get()整个数组或点复制到内存中的同一阵列的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

  =人Backbone.Model.extend({
        默认值:{
            名称:'胎儿',
            年龄:0,
            孩子:[]
        },
        初始化:功能(){
            警报(欢迎来到这个世界上);
        },
        采纳:功能(newChildsName){
            VAR children_array = this.get(孩子);
            children_array.push(newChildsName);
            this.set({孩子:children_array});
        }
    });    变种人=新的Person({名:托马斯,年龄:67,儿童:['瑞恩']});
    person.adopt(约翰Resig的');
    VAR孩子= person.get(孩子); // ['瑞恩','约翰Resig的']

在这个例子中code,我们有:

children_array = this.get(孩子)

我想这将只是指向同一阵列中的内存(因此是O(1))。不过转念一想,这将是一个地板的设计,因为一个可以操纵的阵列,而无需使用this.set(),然后事件侦听器就不会开火。

所以我猜它(不知何故神奇地)复制阵列??

http://backbonejs.org/#Model-set

该怎么办?

编辑:我刚刚发现在骨干源$ C ​​$ C实施<一个href=\"https://github.com/documentcloud/backbone/blob/master/backbone.js\">https://github.com/documentcloud/backbone/blob/master/backbone.js (我已经贴在底部相关code)

获取回报:

 收益this.attributes [ATTR]

所以这将只是指向内存权的同一阵列?因此,人们可能会改变,而无需使用集(阵列),这将是坏..?我是正确的?

 得到:函数(ATTR){
      返回this.attributes [ATTR]
    },    //获取属性的HTML转义值。
    逃跑:功能(ATTR){
      VAR HTML;
      如果(HTML = this._escapedAttributes [ATTR])返回HTML;
      VAR VAL = this.get(attr)使用;
      返回this._escapedAttributes [ATTR = _.escape(VAL == NULL''''+ VAL);
    },    //如果属性包含的值不为空返回TRUE
    //或不确定。
    有:函数(ATTR){
      返回this.get(attr)使用!= NULL;
    },    //设置模型的属性散列的对象,射击`变除非`
    //你选择沉默吧。
    设置:功能(键,值,选项){
      VAR ATTRS,ATTR,VAL;      //同时处理`钥匙,value`和`{键:值}`式的论点。
      如果(_.isObject(密钥)||键== NULL){
        ATTRS =键;
        选项​​=价值;
      }其他{
        的attrs = {};
        ATTRS [关键] =价值;
      }      //提取属性和选项。
      选择|| (选项= {});
      如果返回此(ATTRS!);
      如果(ATTRS的instanceof模型)ATTRS = attrs.attributes;
      如果(options.unset)为(以ATTRS attr)使用ATTRS [ATTR] = 0无效;      //运行验证。
      (!this._validate(ATTRS,期权))如果返回false;      //检查id`的`变化。
      如果(this.idAttribute在ATTRS)this.id = ATTRS [this.idAttribute]      VAR变化= options.changes = {};
      VAR现在= this.attributes;
      VAR逃脱= this._escapedAttributes;
      VAR preV =该._ previousAttributes || {};      //对于每一个`set`属性...
      对(在ATTRS ATTR){
        VAL = ATTRS [ATTR]        //如果新的和当前的值不同,记录更改。
        如果(的isEqual _(现[ATTR],VAL)||(options.unset&安培;!&放大器; _.has(现,ATTR))){
          删除逃脱[ATTR]
          (options.silent this._silent:改变?)[ATTR] =真;
        }        //更新或删除当前值。
        options.unset?现在可以删除[ATTR]:现在[ATTR = VAL;        //如果新和previous值不同,记录更改。如果不,
        //然后删除该属性的变化。
        如果(!_。isEqual:方法(preV [ATTR],VAL)||(_.has(现,attr)使用!== _.has(preV,ATTR))){
          this.changed [ATTR = VAL;
          如果(options.silent!)this._pending [ATTR] =真;
        }其他{
          删除this.changed [ATTR]
          删除this._pending [ATTR]
        }
      }      //火`变`事件。
      如果(options.silent!)this.change(选件);
      返回此;
    },


解决方案

的文档接口实际上并不指定谁拥有数组引用,所以你是你自己在这里。如果你看一下实现,你会看到(像你一样)的 GET 刚刚返回直出的型号的内部属性。这能与不变类型(如数字,字符串和布尔值),但运行到与可变类型的问题,如数组:你可以很容易地改变一些事情,而不必骨干知道关于它的任何方式

骨干机型似乎意在牵制基本类型。

有三个原因叫设置


  1. 这就是接口规范说的事情。

  2. 如果你不叫设置,你不触发事件。

  3. 如果你不叫设置,你会绕过验证逻辑设置了。

您只需要当你使用数组和对象值的工作要小心。

请注意,这种行为 GET 设置是一个实现细节和未来的版本中可能会得到关于如何聪明,他们处理非原始属性值。


与阵列的情况属性(为此事对象属性)实际上是比你差可能最初怀疑。当你说 m.set(P,V),骨干会不会考虑设置是一个变化,如果 v === current_value_of_p ,所以如果你拉出来一个数组:

  VAR一个= m.get(P);

然后修改它:

  a.push(X);

和发回的:

  m.set(P,A);

您不会从模型中获取一个改变事件,因为 A === A ; 使用下划线的的 的isEqual !== 但效果是在这种情况下是相同的。

例如,诈骗的这个简单的一点:

  VAR M = Backbone.Model.extend({});
变种M =新的M({号码:[1]});
m.on('变',函数(){的console.log('改为')});的console.log('设置新的数组');
m.set('P',[2]);的console.log('更改,恕不集');
。m.get('P')推(3);的console.log('GET数组,变更,并重新设置');
VAR一个= m.get('P'); a.push(4); m.set('P',一);的console.log('GET数组,克隆它,改变它,将它设置');
A = _(m.get('P'))的clone()。 a.push(5); m.set('P',一);

产生两个改变事件:一前一后第一个设置和一个最后<$ C $之后C>设置。

演示: http://jsfiddle.net/ambiguous/QwZDv/

如果你看看设置你会发现,有对属性的一些特殊的处理是 Backbone.Model 秒。


这里的基本教训很简单:


  

如果您打算使用可变类型的属性值, _。克隆 他们的出路(或使用 $。扩展(真实,...) 如果你需要一个深层副本),如果有,你会改变数值的任何机会。


 Person = Backbone.Model.extend({
        defaults: {
            name: 'Fetus',
            age: 0,
            children: []
        },
        initialize: function(){
            alert("Welcome to this world");
        },
        adopt: function( newChildsName ){
            var children_array = this.get("children");
            children_array.push( newChildsName );
            this.set({ children: children_array });
        }
    });

    var person = new Person({ name: "Thomas", age: 67, children: ['Ryan']});
    person.adopt('John Resig');
    var children = person.get("children"); // ['Ryan', 'John Resig']

In this example code we have:

children_array = this.get("children")

I was thinking this would just point to the same array in memory (and so would be O(1)). However then I thought that would be a design floor because one could manipulate the array without using this.set() and then event listeners wouldn't fire.

So I'm guessing it (somehow magically) copies the array??

http://backbonejs.org/#Model-set

What happens?

edit: I just found the implementation in the backbone source code at https://github.com/documentcloud/backbone/blob/master/backbone.js (I've pasted relevant code at bottom)

Get returns:

return this.attributes[attr]

so this would just point to the same array in memory right? So one could change the array without using set() and that would be bad.. ? am i correct?

get: function(attr) {
      return this.attributes[attr];
    },

    // Get the HTML-escaped value of an attribute.
    escape: function(attr) {
      var html;
      if (html = this._escapedAttributes[attr]) return html;
      var val = this.get(attr);
      return this._escapedAttributes[attr] = _.escape(val == null ? '' : '' + val);
    },

    // Returns `true` if the attribute contains a value that is not null
    // or undefined.
    has: function(attr) {
      return this.get(attr) != null;
    },

    // Set a hash of model attributes on the object, firing `"change"` unless
    // you choose to silence it.
    set: function(key, value, options) {
      var attrs, attr, val;

      // Handle both `"key", value` and `{key: value}` -style arguments.
      if (_.isObject(key) || key == null) {
        attrs = key;
        options = value;
      } else {
        attrs = {};
        attrs[key] = value;
      }

      // Extract attributes and options.
      options || (options = {});
      if (!attrs) return this;
      if (attrs instanceof Model) attrs = attrs.attributes;
      if (options.unset) for (attr in attrs) attrs[attr] = void 0;

      // Run validation.
      if (!this._validate(attrs, options)) return false;

      // Check for changes of `id`.
      if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];

      var changes = options.changes = {};
      var now = this.attributes;
      var escaped = this._escapedAttributes;
      var prev = this._previousAttributes || {};

      // For each `set` attribute...
      for (attr in attrs) {
        val = attrs[attr];

        // If the new and current value differ, record the change.
        if (!_.isEqual(now[attr], val) || (options.unset && _.has(now, attr))) {
          delete escaped[attr];
          (options.silent ? this._silent : changes)[attr] = true;
        }

        // Update or delete the current value.
        options.unset ? delete now[attr] : now[attr] = val;

        // If the new and previous value differ, record the change.  If not,
        // then remove changes for this attribute.
        if (!_.isEqual(prev[attr], val) || (_.has(now, attr) !== _.has(prev, attr))) {
          this.changed[attr] = val;
          if (!options.silent) this._pending[attr] = true;
        } else {
          delete this.changed[attr];
          delete this._pending[attr];
        }
      }

      // Fire the `"change"` events.
      if (!options.silent) this.change(options);
      return this;
    },

解决方案

The documented interface doesn't actually specify who owns the array reference so you're on your own here. If you look at the implementation, you'll see (as you did) that get just returns a reference straight out of the model's internal attributes. This works fine with immutable types (such as numbers, strings, and booleans) but runs into problems with mutable types such as arrays: you can easily change something without Backbone having any way of knowing about it.

Backbone models appear to be intended to contain primitive types.

There are three reasons to call set:

  1. That's what the interface specification says to do.
  2. If you don't call set, you don't trigger events.
  3. If you don't call set, you'll bypass the validation logic that set has.

You just have to be careful if you're working with array and object values.

Note that this behavior of get and set is an implementation detail and future versions might get smarter about how they handle non-primitive attribute values.


The situation with array attributes (and object attributes for that matter) is actually worse than you might initially suspect. When you say m.set(p, v), Backbone won't consider that set to be a change if v === current_value_of_p so if you pull out an array:

var a = m.get(p);

then modify it:

a.push(x);

and send it back in:

m.set(p, a);

you won't get a "change" event from the model because a === a; Backbone actually uses Underscore's isEqual combined with !== but the effect is the same in this case.

For example, this simple bit of chicanery:

var M = Backbone.Model.extend({});
var m = new M({ p: [ 1 ] });
m.on('change', function() { console.log('changed') });

console.log('Set to new array');
m.set('p', [2]);

console.log('Change without set');
m.get('p').push(3);

console.log('Get array, change, and re-set it');
var a = m.get('p'); a.push(4); m.set('p', a);

console.log('Get array, clone it, change it, set it');
a = _(m.get('p')).clone(); a.push(5); m.set('p', a);​

produces two "change" events: one after the first set and one after the last set.

Demo: http://jsfiddle.net/ambiguous/QwZDv/

If you look at set you'll notice that there is some special handling for attributes that are Backbone.Models.


The basic lesson here is simple:

If you're going to use mutable types as attribute values, _.clone them on the way out (or use $.extend(true, ...) if you need a deep copy) if there is any chance that you'll change the value.

这篇关于不Backbone.Models this.get()整个数组或点复制到内存中的同一阵列的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆