A Javascript OO gotcha

Every now and then I run into this issue when I’m do OO in Javascript. It creates really wierd bugs that can make you want to rip you hair off (I have no hair left…), so I thought it might be a good idea to share it. It all boils down to the fact that objects in Javascript are references by default.

So let’s say you have code like this:

var User = Class.extend({
  _friends: [],
  _name: '',
  init: function(name) {
    this._name = name;
  },
  name: function() {
    return this._name;
  },
  addFriend: function(friend) {
    this._friends.push(friend);
  },
  greetFriends: function() {
    alert('Hi '+ this._friends.join(', ') +'!');
  }
});

var fred = new User('Fred');
var mike = new User('Mike');
var jennifer = new User('Jennifer');
var sara = new User('Sara');

fred.addFriend(jennifer.name());
fred.addFriend(sara.name());

mike.greetFriends();

You would expect that Mike who nobody seems to like would just alert “Hi !”, but he alerts Freds friends as well. And that’s because objects in Javascript (arrays are objects as well) are passed by reference. So all of Freds friends also become Mikes friends, since they share the same friend array.

So to get away from this, the trick is to declare your properties in the constructor, like this:

var User = Class.extend({
  _friends: [],
  _name: '',
  init: function(name) {
    this._name = name;
    this._friends = [];
  },
  name: function() {
    return this._name;
  },
  addFriend: function(friend) {
    this._friends.push(friend);
  },
  greetFriends: function() {
    alert('Hi '+ this._friends.join(', ') +'!');
  }
});

Since the constructor (init) is called every time a new user object is created, the friends array is set for only that instance. But sometimes you might forget to reset the variable in the constructor, so I usually do this:

var User = Class.extend({
  _friends: false,
  _name: '',
  init: function(name) {
    this._name = name;
    this._friends = [];
  },
  name: function() {
    return this._name;
  },
  addFriend: function(friend) {
    this._friends.push(friend);
  },
  greetFriends: function() {
    alert('Hi '+ this._friends.join(', ') +'!');
  }
});

Just to make sure that there’s no risk of a shared array. You might even skip the declaration at the top, but I’m used to looking there for property declarations, so I set them at the top.

I’m sure that you already thought about this by know, but this feature in Javascript makes it possible to have “static” properties that are shared between all instances of the same class. I would advise you to use such a strategy sparsely though, as it’s not very obvious that it’s a shared variable. But the good thing about it is that this Javascript version of static properties doesn’t suffer the same problems that statics do in Java/PHP, since the static can be turned into a local just by resetting it in the constructor, without it affecting other objects. Something like:

var User = Class.extend({
  _friends: [],
  _name: '',
  init: function(name, share_friends) {
    this._name = name;
    if(!share_friends) {
      this._friends = [];
    }
  },
  name: function() {
    return this._name;
  },
  addFriend: function(friend) {
    this._friends.push(friend);
  },
  greetFriends: function() {
    alert('Hi '+ this._friends.join(', ') +'!');
  }
});

var fred = new User('Fred', true);
var mike = new User('Mike', false);
var jennifer = new User('Jennifer', true);
var sara = new User('Sara', true);

fred.addFriend(jennifer.name());
fred.addFriend(sara.name());
mike.addFriend(fred.name());

mike.greetFriends();
sara.greetFriends();

This code now tells us that Mike only has Fred as a friend, whereas Sara shares friends with Fred, and has both herself and Jennifer as her friends.

Happy coding!


About this entry