10 December 2007

Reference types in prototype declaration - JavaScript gotcha

I've recently started doing some heavy duty JavaScript, including messing around with constructors, prototypes, lamdas, closures etc. I started off creating a few classes by creating a constructor function and then defining its prototype using object-literal notation as it means your code looks quite like class declarations in other OO languages.

Here is a simple example without any methods, just with two member variables:

function MyClass(){}
MyClass.prototype = {
 iNum: 0,
 sStr: 'Derek'
};

var oA = new MyClass();
var oB = new MyClass();

// oA: iNum -> 0, sStr -> Derek
// oB: iNum -> 0, sStr -> Derek

oA.iNum++;
oA.sStr += ' Fowler';

// oA: iNum -> 1, sStr -> Derek Fowler
// oB: iNum -> 0, sStr -> Derek

All seems well, however, upon adding some reference types to the prototype things get a bit strange:

//...

MyClass.prototype = {
 iNum: 0,
 sStr: 'Derek',
 aAry: []
};

//...

// oA: aAry.length -> 0
// oB: aAry.length -> 0

oA.aAry.push('test');

// oA: aAry.length -> 1
// oB: aAry.length -> 1

Adding an element to aAry of oA has added it to oB's aAry too. "Odd", I thought, so I did a little more investigation:

// MyClass.prototype.aAry.length -> 1

The array property of the instances is pointing back to the property of the prototype. In other OO languages all member variables within the class declaration are copied into the instances, it is only static variables and method implementations that are shared between them. These JavaScript variables seem to be neither member nor static, they are member if value types and static if reference types. Next I tried looping though the properties and testing hasOwnProperty:

for(var sProp in oA){
 // oA.hasOwnProperty(sProp)
}

Before the assignments all the properties return false, that they are not member properties but reside within the prototype. Following the assignment however iNum and sStr return member and only aAry still claims to be prototype.

This behaviour isn't a bug in an implementation either, it is consistant in all the browsers but after having a quick look in the ECMA-262 doc i'm still none the wiser about why this is the case. Anyone who can shed light on this please comment below.

The solution

The solution to this is to create all member variables using the this keyword in the class constructor thus:

function MyClass(){
 this.aAry = [];
}

This ensures that the variable is created upon the new instance and you don't get any sharing problems.

No comments: