Enumeration and Object Oriented JavaScript

 

*AnimTree
*Tabs
*GlideMenus
*DragLib

The DontEnum attribute

ECMAScript 3 has an internal attribute called DontEnum. This attribute is attached to certain properties by default (§8.6.1).

The internal DontEnum attribute determines what is not to be enumerated by a for-in enumeration (§12.6.4).

propertyIsEnumerable Test

The internal DontEnum attribute can be read by using Object.prototype.propertyIsEnumerable( s ), but cannot be set or modified. Due to a defect in the spec, and defects in the browsers, this method doesn't tell you what will or will not show up in a for in loop. I'll explain why later.

(function(){

var f = function Garrett(){}
f.constructor = "Monkey";
var isEnumerable = f.propertyIsEnumerable('prototype');
var isEnumerable2 = f.propertyIsEnumerable('constructor');
return isEnumerable + " " + isEnumerable2;

})();

Result:

Should be: true true

Browser: Internet Explorer Mozilla Opera Safari 2 Safari 3
Result: false false true true false true error true true

The browsers clearly disagree. There are other browser bugs and problems with the ECMA-262 spec itself, as we will see.

Safari 2 Error?

Safari 2 does not support Object.prototype.propertyIsEnumerable. This is fixed in Safari 3.

The tests in this article use the following function in lieu of Safari's missing Object.prototype.propertyIsEnumerable.

/** For safari 2's missing Object.prototype.propertyIsEnumerable
 */
function isPropertyEnumerable(o, p) {
    if( Object.prototype.propertyIsEnumerable ) {
        return Object.prototype.propertyIsEnumerable.call( o, p );
    }
    
    if(o.hasOwnProperty(p)) {
        for( var prop in o ) {
            if(prop == p) return true;
        }
    }
    return false;
}

The above function is inefficient and is in not presented as a solution for production code. It is simple, it works in Safari 2, and I need it for the examples.

I named it isPropertyEnumerable for two reasons:

  1. Boolean methods start with "is" or "has"
  2. Distinguishes the function from propertyIsEnumerable. This point avoids a nasty bug in Opera, where modifying window.propertyIsEnumerable would have the effect of modifying Object.prototype.propertyIsEnumerable (link).

JScript DontEnum Bug

JScript has a Long-standing Bug with its Implementation of DontEnum

JScript will skip over any property in any object where there is a same-named property in the object's prototype chain that has the DontEnum attribute. If a property with the DontEnum attribute exists in the prototype chain, or if the instance property is marked DontEnum, it is not enumerated, regardless of programmer defined values for that property. JScript does not properly check the DontEnum atribute.

This can be easily demonstrated by creating an object and then enumerating over its properties.

(function(){
var obj = {
    constructor : function() { return 0; }
    ,toString : function() { return "1"; }
    ,valueOf : function() { return 2; }
    ,toLocaleString : function() { return "3"; }
    ,prototype : function() { return "4"; }
    ,isPrototypeOf : function() { return 5; }
    ,propertyIsEnumerable : function() { return 6; }
    ,hasOwnProperty : function() { return 7; }
    ,length: function() { return 8; }
    ,unique : function() { return "9" }
};
 
var result = [];
for(var prop in obj) {
	result.push(obj[ prop ]());
}

return result.join("");
})(); 

Result:

Should be:0123456789

Browser: Internet Explorer Mozilla Opera Safari 2 Safari 3
Result: 489 0123456789 0123456789 0123456789 0123456789

We've got a problem!

The JScript DontEnum bug effectively breaks Object as a hashtable. Quite a serious problem. Nothing will force IE to properly recognize the DontEnum attribute.

It is interesting that IE enumerated the prototype property because Object.prototype has the DontEnum attribute. IE will not enumerate a prototype property for Function objects.

There are some examples later on that show how to borrow methods in Internet Explorer, working around this problem.

Discovery

I first noticed this problem back in 2002. I had a simple test case that seemed to give the wrong result. It was explained to me that toString is non-enumerable, even if overridden. I accepted this advice from Dr. Tom Trenka and Michael van Ouwerkerk, the latter of whom who correctly and concisely described typing in JavaScript: "Variables are not typed in JavaScript, but their values are." (most people misunderstand this). Both intelligent, successful programmers.

Later on, I aggressively pursued the correct answer. I felt that JScript was wrong. I read the ECMAScript-262 specification. It turns out, JScript is wrong, still to this day. §12.6.4 describes the algorithm for for-in enumeration:

§12.6.4 The for-in Statement The production IterationStatement : for ( VariableDeclarationNoIn in Expression ) Statement is evaluated as follows:

  1. Evaluate VariableDeclarationNoIn.
  2. Evaluate the Expression.
  3. Call GetValue(Result(2)).
  4. Call ToObject(Result(3)).
  5. Let V = empty.
  6. Get the name of the next property of Result(4) that doesn't have the DontEnum attribute.
  7. Evaluate Result(1) as if it were an Identifier; (yes, it may be evaluated repeatedly).
  8. Call PutValue(Result(7), Result(6)).
  9. ...

The problem is that JScript does not properly check the DontEnum attribute in step 6. Get the name of ( the next property of Result(3) that doesn't have the DontEnum attribute ).

Instead of checking the DontEnum attribute, JScript will skip over any property in any object where there is a same-named property in the object's prototype chain that has the attribute DontEnum.

To understand what this means, it is necessary to define what the prototype chain is.

Next in this tutorial: