ECMAScript 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;
})();
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 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.

JScript DontEnum Bug

JScript had a Long-standing Bug with its Implementation of DontEnum.

In IE < 9, 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.

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("");
})(); 

Should be: 0123456789

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

We've got a problem!

Internet Explorer's ECMAScript support document explains the DontEnum issue in a note at the bottom of Section 2.1.40 [ECMA-262] Section 12.6.4, The for-in Statement :

Note that JScript 5.x under Internet Explorer < 9 defines properties (see [ECMA-262] section 6.6.2.2) such that their DontEnum attribute is inherited from prototype properties with the same name. As a result of this, any properties that have the same name as built-in properties of a prototype object that have the DontEnum attribute are not included in an enumeration. However JScript 5.x under Internet Explorer 9 includes the properties that have the same name as built-in properties of a prototype object in an enumeration.

Borrowing

Libraries often want to use inter-type declarations, and provide generic functionality for doing so. An inter-type declaration is basically "borrowing" (a) method(s) or property(s) from a supplier object. Yahoo calls this "augmenting" Dojo calls it "extending" ("extend" implies inheritance, so it's not an appropriate term).

for( var prop in supplier ) 
  receiver[ prop ] = supplier[ prop ];

Using for in to copy all properties will fail in Internet Explorer, due to the JScript DontEnum bug.

Blindly copying all properties (including those in the prototype) may have results that the user of the "borrow" code did not want or consider. For these reasons, it's a good idea to consider explicit inter-type declarations.

function crossCut(receiver, supplier, propsArray, borrowFilter) {
    if(typeof propsArray == "string") propsArray = [propsArray];
    for(var i = 0, len = propsArray.length; i < len; i++) {
        var p = propsArray[i];
        if(! (p in supplier)) {
            var err = Error(p + " not found in supplier. Did you intend to borrow from the prototype?");
            throw err;
        }
        receiver[p] = supplier[p];
    }
}

By using explicit inter-type declarations, programs avoid all of the pitfalls with the JScript bug and the inherent complications of enumerating over the prototype chain. This simple function can be modified to use a borrowFilter, which can provide AOP context resolver, AOP advice, or source code rewriting, should such functionality be needed.

Looking Forward

The fact that the DontEnum attribute is not settable is a problem that is positively addressed in ECMAScript 4.

// Make a property have the DontEnum attribute.
myObj.propertyIsEnumerable("toString", false);

ECMAScript 4 also provides a native Map<T,T>. Map<T,T> gives developers the option of using a typed collection (fast) that has none of the problems of enumerating over the prototype chain, but also has a more sophisticated iteration strategy that can be confusing.

Document Tags and Contributors

Contributors to this page: Sheppy, Dhtmlkitchen@gmail.com, jdalton, Nickolay, fscholz
Last updated by: fscholz,