JavaScript gotcha: inherited properties do not get created until assignment

Everyone know JavaScript has some weird gotchas, such as the already infamous JavaScript equality table. While a good framework / compiled language (such as LiveScript) will let you easily avoid a lot of them, there are things inherent to the way JS works that you can’t really sidestep. One example: properties defined on the prototype.

Let’s say we have a simple constructor function:

var ctor = function() {
  ctor.prototype.number = 15;

  ctor.prototype.increaseNumber = function() {
    this.number += 5;
  }
}

Now, here’s what would happen

var obj1 = new ctor();
var obj2 = new ctor();
obj1.number // 15
obj1.increaseNumber();
obj1.number // 20

obj2.number // 15

Everything as it should be. However, if the property was an object, and instead of setting its value you accessed it’s properties, something weird would happen:

ctor = function() {
  ctor.prototype.testObj = {};
  
  ctor.prototype.changeTestObj = function() {
    this.testObj.testValue = 50;
  }
}

obj1 = new ctor();
obj2 = new ctor();
obj1.changeTestObj();
obj1.testObj.testValue; // 50

obj2.testObj.testValue; // 50 - WTF!?

Those better at thinking in JavaScript probably aren’t surprised, but I sure was. Why would the value be modified on the instance in one case, but on the prototype on the other? The answer is, because JavaScript only creates instance copies on assignment. And since ctor.testObj is never actually assigned to from the object instance — only its property is accessed — JavaScript doesn’t create a new instance property and instead modifies the one in the prototype. Which means it modifies every “new ctor()” instance everywhere in the code.

The solution? Set the instance value in addition to prototype value in the constructor, if you must use prototype at all.

ctor = function() {
  // Do NOT use x = y = 12 assignment syntax, you'll just end up with the same
  // problem because of both instance and prototype properties pointing to the
  // same object
  this.testObj = {}; 
  ctor.prototype.testObj = {};
  
  ...
}

Leave a Reply

Your email address will not be published. Required fields are marked *