Делает Backbone.Models this.get() копирует весь массив или указывает на тот же массив в памяти
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']
В этом примере кода мы имеем:
children_array = this.get( "children" )
Я думал, что это просто укажет на тот же массив в памяти (и так будет O (1)). Однако тогда я думал, что это будет дизайн, потому что можно манипулировать массивом без использования this.set(), а затем прослушиватели событий не будут срабатывать.
Итак, я предполагаю, что это (каким-то волшебным образом) копирует массив??
http://backbonejs.org/#Model-set
Что происходит?
edit: Я только что нашел реализацию в базовом исходном коде https://github.com/documentcloud/backbone/blob/master/backbone.js (я вставил соответствующий код внизу)
Получить возврат:
return this.attributes[attr]
так что это просто укажет на тот же массив в памяти? Таким образом, можно изменить массив без использования set(), и это было бы плохо..? я прав?
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;
},
Ответы
Ответ 1
Задокументированный интерфейс фактически не указывает, кому принадлежит ссылка на массив, поэтому вы сами по себе. Если вы посмотрите на реализацию, вы увидите (как и вы), что get
просто возвращает ссылку прямо из внутренней модели attributes
. Это отлично работает с неизменяемыми типами (такими как числа, строки и логические значения), но сталкивается с проблемами с изменяемыми типами, такими как массивы: вы можете легко что-то изменить без поддержки Магистра, имея какой-либо способ узнать об этом.
Модели с базой, по-видимому, предназначены для размещения примитивных типов.
Существует три причины вызова set
:
- То, что говорит спецификация интерфейса.
- Если вы не вызываете
set
, вы не запускаете события.
- Если вы не вызываете
set
, вы обойдете логику проверки, которая имеет set
.
Вам просто нужно быть осторожным, если вы работаете с массивами и объектами.
Обратите внимание, что это поведение get
и set
- это деталь реализации, и будущие версии могут получить более умное представление о том, как они обрабатывают значения, не являющиеся примитивными атрибутами.
Ситуация с атрибутами массива (и атрибутами объекта, если на то пошло) на самом деле хуже, чем вы могли бы подозревать вначале. Когда вы скажете m.set(p, v)
, Backbone не будет считать, что set
будет изменением, если v === current_value_of_p
, поэтому, если вы вытащите массив:
var a = m.get(p);
затем измените его:
a.push(x);
и отправьте его обратно:
m.set(p, a);
вы не получите событие "change"
из модели, потому что a === a
; Backbone фактически использует Underscore isEqual
в сочетании с !==
но в этом случае эффект будет таким же.
Например, этот простой бит 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);
создает два события "change"
: один после первого set
и один после последнего set
.
Демо: http://jsfiddle.net/ambiguous/QwZDv/
Если вы посмотрите на set
, вы заметите, что есть некоторая специальная обработка для атрибутов Backbone.Model
s.
Основной урок здесь прост:
Если вы собираетесь использовать изменяемые типы в качестве значений атрибутов, _.clone
их на выходе (или используйте $.extend(true, ...)
, если вам нужна глубокая копия), если есть вероятность, что вы измените значение.