Делает Backbone.Models this.get() копирует весь массив или указывает на тот же массив в памяти - программирование
Подтвердить что ты не робот

Делает 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;
    },
4b9b3361

Ответ 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, ...), если вам нужна глубокая копия), если есть вероятность, что вы измените значение.