5

I spent a lot of time trying to catch a bug in my app. Eventually I set apart this piece of code which behavior seems very strange to me.

var Model = Backbone.Model.extend({
    myProperty: []
});

var one = new Model();
var two = new Model();
one.myProperty.push(1);
console.log(two.myProperty); //1!!

What's the reason behind it? Why it acts so? How to avoid this type of bugs in code?

alexb
  • 880
  • 11
  • 16

3 Answers3

3

Inheritance in JavaScript is prototypical - objects can refer directly to properties higher up in the prototype chain.

In your example, one and two both share a common prototype, and do not provide their own values for myProperty so they both refer directly to Model.protoype.myProperty.

You should create new myProperty array for each model you instantiate. Model.initialize is the idiomatic place for this kind of initialisation - overriding constructor is unnecessarily complex.

var Model = Backbone.Model.extend({
    initialize: function() {
      this.myProperty = [];
    }
});

Alternatively you could make myProperty as an attribute of the model:

var Model = Backbone.Model.extend({
    defaults: function() {
      return {
        myProperty: []
      }
    }
});

It is important to note that defaults is a function - if you were to use a simple object you would encounter the same shared reference issue.

joews
  • 29,767
  • 10
  • 79
  • 91
  • Thanks! I thought of initializing myProperty in initialize (sorry for tautology :D), but it seemed strange to me that initializing it in another way leads to different results. At least it's semantically not obvious... But it's javascipt problem, not Backbone, I guess. – alexb Dec 19 '14 at 18:49
  • 1
    Exactly - the key point is that is a JavaScript, not a Backbone thing. It's not inherently a problem, in fact it's completely necessary for a prototype system - without it, your Model instances could not share methods or common properties without creating a lot of duplicate objects. It does cause this type of bug very often though! – joews Dec 19 '14 at 19:15
1

The documentation says:

constructor / initialize new Model([attributes], [options])

When creating an instance of a model, you can pass in the initial values of the attributes, which will be set on the model. If you define an initialize function, it will be invoked when the model is created.

In rare cases, if you're looking to get fancy, you may want to override constructor, which allows you to replace the actual constructor function for your model.

So, following the documentation, you'd want to do something like this to get your case running:

var Model = Backbone.Model.extend({
    initialize: function() {
        this.myProperty = [];
    }
});

source: http://backbonejs.org/#Model-extend

blgt
  • 8,135
  • 1
  • 25
  • 28
  • 2
    Why would you override `constructor` to provide the same functionality of `initialize`? – Jivings Dec 19 '14 at 15:43
  • 1
    @Jivings Because you're copy-pasting from the documentation page. Edited `initialize` into post – blgt Dec 19 '14 at 16:02
1

Actually its because myProperty is an array, and as you know arrays will be stored by reference. Just to test consider the following code:

var Model = Backbone.Model.extend({
 myProperty: [],
 messege: ''
});

var one = new Model();
var two = new Model();

one.messege = 'One!';
two.messege = 'Two!';

console.log(one.messege ); // 'One!'
console.log(two.messege ); // 'Two!'

An alternative around this could be:

var Model = Backbone.Model.extend({
  constructor: function() {
    this.myProperty = [];
    Backbone.Model.apply(this);
  }
});

var one = new Model();
one.myProperty.push(1);

var two = new Model();
console.log(two.myProperty); // []
sadrzadehsina
  • 1,331
  • 13
  • 26
  • 1
    It's important to point out that the reason `one` and `two` share a reference is prototypical inheritance - the child objects share a reference to a parent property. – joews Dec 19 '14 at 15:46
  • 1
    Also, Backbone provides `Model.initialize` for this kind of initialisation - overriding `constructor` is overkill. The Backbone docs say "In rare cases, if you're looking to get fancy, you may want to override constructor...". This isn't a rare case - it's the exact common use case that `initialize` is intended for. – joews Dec 19 '14 at 15:48
  • @joews That's right, `initialize` is a better choice in this case. – sadrzadehsina Dec 19 '14 at 16:30
  • But I will keep my answer unchanged to search more about `constructor` in backbone models. – sadrzadehsina Dec 19 '14 at 16:32