Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

does Backbone.Models this.get() copy an entire array or point to the same array in memory

 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;
    },
like image 616
robert king Avatar asked Jul 26 '12 02:07

robert king


People also ask

How does Backbone JS work?

Backbone. js gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.

Which of the following is the correct syntax for creating backbone collection with model model?

The Backbone. js collection models specify the array or models which are created inside of a collection. Syntax: collection.

When backbone wants to save or read a model to the server it calls out a function called as?

10) What is the usage of Backbone. sync? When Backbone. js wants to save or read a model to the Server, it calls out a function called Backbone.

What is backbone in programming?

Backbone. js is a model view controller (MVC) Web application framework that provides structure to JavaScript-heavy applications. This is done by supplying models with custom events and key-value binding, views using declarative event handling and collections with a rich application programming interface (API).


1 Answers

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.

like image 77
mu is too short Avatar answered Oct 26 '22 15:10

mu is too short