SlightlyLoony
Tera Contributor

One of my colleagues (Aleck Lin) brought an interesting phenomenon to my attention on Friday: sometimes JavaScript instance variables behave exactly like class variables. What's up with that?!? It turns out to be a result of a slightly subtle aspect of the Prototype.js library, which we use extensively (both on the server and in the browser). Here's an example of the unexpected behavior — and an easy way to work around it (also an explanation for how that photo is related to "unexpected behavior":

First, here's a code example that demonstrates the issue (copy to Scripts - Background and try it!):


var MyObj = Class.create();
MyObj.prototype = {
myNumber: 1,
myArray: [],
initialize: function(n, s) {
this.myNumber += n;
this.myArray.push(s);
}
}

var a = new MyObj(1, 'a');
var b = new MyObj(2, 'b');
gs.log('a.myNumber: ' + a.myNumber);
gs.log('a.myArray: ' + a.myArray);
gs.log('b.myNumber: ' + b.myNumber);
gs.log('b.myArray: ' + b.myArray);

What's so evil about this? Well, take a close look at the results:

*** Script: a.myNumber: 2
*** Script: a.myArray: a,b
*** Script: b.myNumber: 3
*** Script: b.myArray: a,b
The instance variable myNumber is different in the two instances: each of them had a different value added in the initialize() method, just as you'd expect. They're instance variables, after all — so each instance should have its own copy. But take a look at the myArray instance variable: it's the same in both instances, and the values from both instance's initialize() method were pushed into it. What the $#%&!&? is going on here? That instance variable is acting exactly as if it was a class variable!

Well, it turns out that way in which Prototype.js creates the constructor function ends up evaluating what you assign to the prototype property just once. That means, in turn, than any objects you assign to properties are evaluated just once — and then a reference to that object is created for each instance. This is not the same thing that ordinarily happens in a constructor function (without using Prototype.js); normally the prototype property is evaluated as each instance is created. Slightly subtle, and problematic if you really wanted instance objects!

Fortunately, there's a very simple way to get rid of this problem — and this works both with and without Prototype.js:

var MyObj = Class.create();
MyObj.prototype = {
myNumber: 1,
myArray: null,
initialize: function(n, s) {
this.myArray = [];
this.myNumber += n;
this.myArray.push(s);
}
}

var a = new MyObj(1, 'a');
var b = new MyObj(2, 'b');
gs.log('a.myNumber: ' + a.myNumber);
gs.log('a.myArray: ' + a.myArray);
gs.log('b.myNumber: ' + b.myNumber);
gs.log('b.myArray: ' + b.myArray);

Results:

*** Script: a.myNumber: 2
*** Script: a.myArray: a
*** Script: b.myNumber: 3
*** Script: b.myArray: b

Ah, that's more like it!

All I did was to move the initialization of myArray into the initialize() method, and voila! the problem is gone.

How is that photo related to "unexpected behavior"? Consider: it was taken in Iran!