JS Incremental Operator Flux

2010-01-21

Javascript, like so many other languages, supports the unary incremental operator (++). It supports both the prefix and postfix versions.

Another blog post about Common Javascript Problems hinted me to an obscure problem you might not figure out on your own. Or a hack which could be used to obscure your code...

What's going on? Let me show you... This concerns the Postfix Increment Operator, as described by Ecmascript 5, paragraph 11.3.1 (but the same applies to the prefix operator in terms of result in this context). This operator basically increments the value of the variable, returns the current value of the variable and updates the value of the variable by one.

Code: (JS)
var n = 5;
alert(n); // 5
alert(++n); // 6
alert(n); // 6
alert(n++); // 6
alert(n); // 7

Nothing unintuitive here.

Now the specification says it will first get the value (GetValue) of the LeftHandSideExpression, then convert that to a number (ToNumber) and use that as oldValue. Basically it does parseFloat(lhs.valueOf())... This will work fine on objects... It will then add one to oldValue and store the result in newValue. The postfix operator then calls PutValue(LeftHandSideExpression, newValue) and return oldValue. The calling of PutValue means the previous value of the expression would be overwritten (unless it was a setter). So this example would have unintuitive results...

Code: (JS)
var N = function(){
this.n = 5;
this.valueOf = function(){ return this.n; };
this.toString = function(){ return 'N'; };
};
var n = new N();
alert(n); // N
alert(typeof n); // 'object'
alert(n++); // 5
alert(n); // 6
alert(typeof n); // 'number'

The prefix increment operator has the same problem, as it also stores the result in the variable it increments.

Now, to solve this problem, one could try to use the setter and getter properties of the (new) Ecmascript 5 specification. These are variables which are not just variables, but they execute a hidden function when you get or set them (much like the __Get and __Set functions in PHP). See the next example

Code: (JS)
var n = {
obj: new N(),
set test(n){ this.obj.n = n; },
get test(){ return this.obj; }
};

The main drawback of this is that you can only define these as properties of new objects. This means you can't create a global (or local) variable this way, as you cannot transfer these objects while retaining their getter and setter properties. Mozilla defined a special function to do this anyways called __defineGetter__ and __defineSetter__, but that did not make it into the standard. It's supported by most non-IE browsers though. You could try Object.defineProperty(), but that's currently not supported by any browser...

Anyways, the same example with the getter/setter:

Code: (JS)
var N = function(){
this.n = 5;
this.valueOf = function(){ return this.n; };
this.toString = function(){ return 'N'; };
};
var n = {
obj: new N(),
set test(n){ this.obj.n = n; },
get test(){ return this.obj; }
};
alert(n.test); // N
alert(typeof n.test); // 'object'
alert(n.test++); // 5
alert(n.test); // N
alert(typeof n.test); // 'object'
alert(n.test.valueOf()); // 6

Note that getters and setters are part of the ES5 spec (and I believe ES3.1 spec as well?), which is (are) not implemented in most current browsers...

Hope it helps you :)