ECMAScript DontEnum attribute

  • Revision slug: ECMAScript_DontEnum_attribute
  • Revision title: ECMAScript DontEnum attribute
  • Revision id: 63938
  • Created:
  • Creator: Dhtmlkitchen@gmail.com
  • Is current revision? No
  • Comment /* Borrowing */

Revision Content

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;

})();

eval

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

eval

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.

Discovery

JScript has never fixed this bug. §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.

ach object has an associated Prototype object (§4.3.5, §8.6.2).

8.6.2 Internal Properties and Methods Native ECMAScript objects have an internal property called Prototype. The value of this property is either null or an object and is used for implementing inheritance. Properties of the Prototype object are visible as properties of the child object for the purposes of Get access, but not for Put access.

The prototype chain (§4.2.1) is a linkage of Prototype objects

4.3.5 Prototype A Prototype is an object used to implement structure, state, and behaviour inheritance in ECMAScript. When a constructor creates an object, that object implicitly references the constructor's associated prototype for the purpose of resolving property references. The constructor's associated prototype can be referenced by the program expression constructor.prototype, and properties added to an object's prototype are shared, through inheritance, by all objects sharing the prototype.

JavaScript uses the prototype chain, a linkage of the internal Prototype objects, with the internal Get(P) method (§8.6.2.1) to resolve property reference identifiers.

For Put access (assignment), the prototype is not used.

function X(){

}

X.prototype.inited = false;

var y = new X;
y.inited = true;

var z = new X;
z.inited; 

The assignment y.inited = false does not change the value of X.prototype.inited, so z.inited is false.


Borrowing

Libraries often want to use intertype declarations, and provide generic functionality for doing so. An intertype declaration is basically "borrowing" (a) method(s) or property(s) from a supplier object. Yahoo calls this "augmenting" Dojo calls it "extending" (extending is the wrong 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 intertype declarations.

function crossCut(receiver, supplier, propsArray, <var>borrowFilter</var>) {
    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 intertype 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 <a href="http://wiki.ecmascript.org/doku.php?id=proposals:enumerability">positively addressed in ECMAScript 4</a>.

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

ECMAScript 4 also provides a native Map<T,T>. This gives developers the option of using a typed collection (fast) that has none of the problems of enumerating over the prototype chain.

Revision Source

<p>ECMAScript 3 has an internal attribute called DontEnum. This attribute is attached to certain properties by default (§8.6.1).
</p><p>The internal DontEnum attribute determines what is not to be enumerated by a for-in enumeration (§12.6.4).
propertyIsEnumerable Test
</p><p>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.
</p>
<pre>(function(){

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

})();
</pre>
<p>eval
</p>
<table border="1" class="result">
		<tbody><tr>

			<th scope="row">Browser:</th>
			<th>Internet Explorer</th>
			<th>Mozilla</th>
			<th>Opera</th>
			<th>Safari 2</th>
			<th>Safari 3</th>
		</tr>
		<tr>
			<th scope="row">Result:</th>
			<td class="incorrect">false false</td>
			<td>true true</td>
			<td class="incorrect">false true</td>
			<td class="error">error</td>
			<td>true true</td>
		</tr>
</tbody></table>
<p>The browsers clearly disagree. There are other browser bugs and problems with the ECMA-262 spec itself, as we will see.
</p>
<h4 name="Safari_2_Error.3F"> Safari 2 Error? </h4>
<p>Safari 2 does not support Object.prototype.propertyIsEnumerable. This is fixed in Safari 3.
</p><p><br>
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.
</p><p><br>
</p>
<h3 name="JScript_DontEnum_Bug"> JScript DontEnum Bug </h3>
<p>JScript has a Long-standing Bug with its Implementation of DontEnum
</p><p>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 attribute.
</p><p>This can be easily demonstrated by creating an object and then enumerating over its properties.
</p>
<pre>(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("");
})(); 
</pre>
<p>eval
</p><p>Should be:0123456789
</p>
<table border="1" class="result">
		<tbody><tr>
			<th scope="row">Browser:</th>
			<th>Internet Explorer</th>
			<th>Mozilla</th>
			<th>Opera</th>
			<th>Safari 2</th>
			<th>Safari 3</th>
    	</tr>
		<tr>
			<th scope="row">Result:</th>
			<td class="incorrect">489</td>
			<td>0123456789</td>
			<td>0123456789</td>
			<td>0123456789</td>
			<td>0123456789</td>
		</tr>
</tbody></table>
<p>We've got a problem!
</p><p>The JScript DontEnum bug effectively breaks Object as a hashtable. Quite a serious problem. Nothing will force IE to properly recognize the DontEnum attribute.
</p><p>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.
</p><p>Discovery
</p><p>JScript has never fixed this bug. §12.6.4 describes the algorithm for for-in enumeration:
</p>
<pre class="eval">   12.6.4 The for-in Statement
</pre>
<p>The production IterationStatement : for ( VariableDeclarationNoIn in Expression ) Statement is evaluated as follows:
</p>
<pre class="eval">      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. ...
</pre>
<p>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 ).
</p><p>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.
</p><p>To understand what this means, it is necessary to define what the prototype chain is.
</p><p>ach object has an associated <a href="en/Prototype">Prototype</a> object (§4.3.5, §8.6.2).
</p>
<blockquote>
    8.6.2 Internal Properties and Methods

    Native ECMAScript objects have an internal property called <a href="en/Prototype">Prototype</a>. The value of this property is either null or an object and is used for implementing inheritance. Properties of the <a href="en/Prototype">Prototype</a> object are visible as properties of the child object for the purposes of <a href="en/Get">Get</a> access, but not for <a href="en/Put">Put</a> access.
</blockquote>
<p>The prototype chain (§4.2.1) is a linkage of <a href="en/Prototype">Prototype</a> objects
</p>
<blockquote>
    4.3.5 Prototype

    A <a href="en/Prototype">Prototype</a> is an object used to implement structure, state, and behaviour inheritance in ECMAScript. When a constructor creates an object, that object implicitly references the constructor's associated prototype for the purpose of resolving property references. The constructor's associated prototype can be referenced by the program expression constructor.prototype, and properties added to an object's prototype are shared, through inheritance, by all objects sharing the prototype.
</blockquote>
<p>JavaScript uses the prototype chain, a linkage of the internal <a href="en/Prototype">Prototype</a> objects, with the internal <a href="en/Get">Get</a>(P) method (§8.6.2.1) to resolve property reference identifiers.
</p><p>For <a href="en/Put">Put</a> access (assignment), the prototype is not used.
</p>
<pre>function X(){

}

X.prototype.inited = false;

var y = new X;
y.inited = true;

var z = new X;
z.inited; 
</pre>
<p>
 The assignment <code>y.inited = false</code> does not change the value of <code>X.prototype.inited</code>, so <code>z.inited</code> is <code>false</code>. 
</p>
<p><br>
</p>
<h3 name="Borrowing"> Borrowing </h3>
<p>Libraries often want to use intertype declarations, and provide generic functionality for doing so. An intertype declaration is basically "borrowing" (a) method(s) or property(s) from a supplier object. Yahoo calls this "augmenting" Dojo calls it "extending" (extending is the wrong term).
</p>
<pre>for( var prop in supplier ) 
  receiver[ prop ] = supplier[ prop ];
</pre>
<p>Using <code>for in</code> to copy all properties will fail in Internet Explorer, due to the JScript DontEnum bug. 
</p><p>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 intertype declarations.
</p>
<pre>function crossCut(receiver, supplier, propsArray, &lt;var&gt;borrowFilter&lt;/var&gt;) {
    if(typeof propsArray == "string") propsArray = [propsArray];
    for(var i = 0, len = propsArray.length; i &lt; 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];
    }
}
</pre>
<p>By using explicit intertype 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 <code>borrowFilter</code>, which can provide AOP context resolver, AOP advice, or source code rewriting, should such functionality be needed.
</p>
<h3 name="Looking_Forward"> Looking Forward </h3>
<p>The fact that the DontEnum attribute is not settable is a problem that is &lt;a href="http://wiki.ecmascript.org/doku.php?id=proposals:enumerability"&gt;positively addressed in ECMAScript 4&lt;/a&gt;.
</p>
<pre>// Make a property have the DontEnum attribute.
myObj.propertyIsEnumerable("toString", false);
</pre>
<p>ECMAScript 4 also provides a native Map&lt;<var>T</var>,<var>T</var>&gt;. This gives developers the option of using a typed collection (fast) that has none of the problems of enumerating over the prototype chain.
</p>
Revert to this revision