不Backbone.Models this.get()整个数组或点复制到内存中的同一阵列 [英] does Backbone.Models this.get() copy an entire array or point to the same array in memory
问题描述
=人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
刚刚返回直出的型号的内部属性参考code>。这能与不变类型(如数字,字符串和布尔值),但运行到与可变类型的问题,如数组:你可以很容易地改变一些事情,而不必骨干知道关于它的任何方式
骨干机型似乎意在牵制基本类型。
有三个原因叫设置
:
- 这就是接口规范说的事情。
- 如果你不叫
设置
,你不触发事件。 - 如果你不叫
设置
,你会绕过验证逻辑设置
了。
您只需要当你使用数组和对象值的工作要小心。
请注意,这种行为 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 internalattributes
. 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
:
- That's what the interface specification says to do.
- If you don't call
set
, you don't trigger events.- If you don't call
set
, you'll bypass the validation logic thatset
has.You just have to be careful if you're working with array and object values.
Note that this behavior of
get
andset
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 thatset
to be a change ifv === 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 becausea === a
; Backbone actually uses Underscore'sisEqual
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 firstset
and one after the lastset
.Demo: http://jsfiddle.net/ambiguous/QwZDv/
If you look at
set
you'll notice that there is some special handling for attributes that areBackbone.Model
s.
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屋!