2012-07-26 9 views
16
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'] 

En este código de ejemplo tenemos:hace Backbone.Models this.get() copiar toda una matriz o punto a la misma matriz en la memoria

children_array = this.get ("niños")

Estaba pensando que esto solo señalaría la misma matriz en la memoria (y entonces sería O (1)). Sin embargo, entonces pensé que sería un piso de diseño porque se podría manipular la matriz sin usar this.set() y entonces los oyentes del evento no se dispararían.

Así que supongo que (de alguna manera mágicamente) copia la matriz?

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

¿Qué ocurre?

editar: Me acaba de encontrar la aplicación en la columna vertebral de código fuente en https://github.com/documentcloud/backbone/blob/master/backbone.js (He pegado el código correspondiente en la parte inferior)

obtener retornos:

return this.attributes[attr] 

lo que esta sería simplemente apunte a la misma matriz en memoria ¿verdad? ¿Entonces uno podría cambiar la matriz sin usar set() y eso sería malo? ¿Estoy en lo correcto?

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

"así que esto solo apunta a la misma matriz en la memoria ¿verdad?" si y si. –

+0

@muistooshortderecha - estaba saltando la pistola. ¿Entonces la razón para usar .set() es solo para disparar eventos? Sería bueno si pudieras resumir en una respuesta corta. –

Respuesta

32

La interfaz documentada en realidad no especifica a quién pertenece la referencia de la matriz por lo que está solo aquí. Si observa la implementación, verá (como lo hizo) que get simplemente devuelve una referencia directamente del attributes interno del modelo. Esto funciona bien con tipos inmutables (como números, cadenas y booleanos), pero tiene problemas con los tipos mutables como las matrices: puede cambiar fácilmente algo sin que Backbone tenga alguna forma de conocerlo.

Los modelos de red troncal parecen estar destinados a contener tipos primitivos.

Hay tres razones para llamar set:

  1. Eso es lo que la especificación de la interfaz dice que hacer.
  2. Si no llama al set, no desencadenará eventos.
  3. Si no llama al set, omitirá la lógica de validación que tiene set.

Solo debe tener cuidado si trabaja con valores de matriz y objeto.

Tenga en cuenta que este comportamiento de get y set es un detalle de la implementación y las versiones futuras podrían ser más inteligentes acerca de cómo manejan valores de atributos no primitivos.


La situación con los atributos de matriz (y los atributos del objeto para el caso) es en realidad peor de lo que inicialmente sospechaba.Cuando usted dice m.set(p, v), Backbone no considerará que set a haber un cambio, si v === current_value_of_p por lo que si usted saca una matriz:

var a = m.get(p); 

luego modificarla:

a.push(x); 

y enviarlo de vuelta en:

m.set(p, a); 

no se va a un evento "change" del modelo porque a === a; Backbone actually uses Underscore's isEqual combinado con !== pero el efecto es el mismo en este caso.

Por ejemplo, este sencillo poco de trapacería:

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);​ 

produce dos eventos: uno "change" después de la primera set y uno después de la última set.

Demostración: http://jsfiddle.net/ambiguous/QwZDv/

Si nos fijamos en set se dará cuenta de que hay algún tratamiento especial para los atributos que son Backbone.Model s.


La lección básica aquí es simple:

Si usted va a utilizar tipos mutables como valores de atributos, _.clone ellos a la salida (o utilizar $.extend(true, ...) si necesita una copia de profundidad) si hay alguna posibilidad de que cambie el valor.

+1

Gracias - Esto me da mucha más confianza en mis corazonadas iniciales y más. strong +1 –

+1

¡Esto es genial y me ayudó a aclarar un montón de confusión sobre esto! Se me ocurre que la única pieza que falta es que un evento de cambio se disparará incluso si no se produce ningún cambio (ya que el conjunto() seguirá cambiando el estado del atributo, independientemente de si los datos han cambiado o no). Para una solución, sugiero comparar las dos matrices, la nueva y la actual en el atributo, antes de decidir si se llama a set(). Esto se puede hacer usando la _.difference() del subrayado. Información aquí: http://stackoverflow.com/questions/11755313/compare-two-arrays-if-keys-match-with-underscore-js – SuperDuperApps

+1

Gracias @ mu-es-demasiado-corto, buena respuesta! Su demostración ya no funciona, así que hice un fork si alguien quiere verificar el comportamiento descrito: [violín] (http://jsfiddle.net/petjofi/m07thgr9/1/) –

Cuestiones relacionadas