Enumeration and Object Oriented JavaScript

 

*AnimTree
*Tabs
*GlideMenus
*DragLib

Object.prototype.propertyIsEnumerable

This method returns true if the property does not have the DontEnum attribute and would be enumerated in a for in loop.

This method does not consider objects in the prototype chain.

This method cannot be used reliably because Internet Explorer does not properly check the DontEnum attribute.

Real World Example

There aren't many real world uses of propertyIsEnumerable on the web. Due to a bug in the spec, it is not a very useful method.

One popular script where propertyIsEnumerable was used is the YUI Connection Manager. The code itself is broken and should not be used as an example of what to do. It is one of the only uses of propertyIsEnumerable in production code.

// connection.js 
for(var prop in this._http_header){
    if(this._http_header.propertyIsEnumerable){
        o.conn.setRequestHeader(prop, this._http_header[prop]);
    }
}

In the code above, the property _http_header is a collection. The proceeding property access operator, ".", will attempt to resolve the property propertyIsEnumerable. If it can, then that value will be returned, otherwise, the value undefined will be returned. The value undefined is returned in Safari 2 because Safari 2 does not support propertyIsEnumerable. In IE, Safari 3, Opera, and Mozilla, the property will be found on Object.prototype. This value, a function, is returned. This value is then evaluated in boolean context in the if statement.

To sum it up, this will have the net effect of filtering out _http_header properties for all browsers that don't support propertyIsEnumerable (Safari 2), and will have no effect, other than a useless and inefficient call up the prototype chain and an extra call to [[ToObject]] on each _http_header and [[ToBoolean]] on the native function for each interim String object with the _http_header value for browsers that do support propertyIsEnumerable. It was obviously not the author's intention to filter out http headers for Safari and reduce performance by increasing overhead, but instead a misunderstanding of the propertyIsEnumerable method (it's not a property) and a lack of testing.

Object.prototype.propertyIsEnumerable is Borken

For user-defined object types, Object.prototype.propertyIsEnumerable, as defined by ECMA-262, offers nothing more than Object.prototype.hasOwnProperty. This is due to a fault in the ECMAScript specification.

(§15.2.4.7) Object.prototype.propertyIsEnumerable (V) When the propertyIsEnumerable method is called with argument V, the following steps are taken:

  1. Let O be this object.
  2. Call ToString(V).
  3. If O doesn't have a property with the name given by Result(2), return false.
  4. If the property has the DontEnum attribute, return false.
  5. Return true.

In JScript, step #4 is is ignored. Some user-defined objects are not enumerable. We have seen this in the for in loop. We can show this by testing propertyIsEnumerable.

The Problem with the Spec

NOTE

This method does not consider objects in the prototype chain.

It is impossible to create a new property that has the DontEnum attribute. All user defined properties are therefore, enumerable.

For user-defined objects in ES3 (and only user-defined objects), propertyIsEnumerable is equivalent to hasOwnProperty. Of course, IE will still exhibit the DontEnum bug which is exposed in propertyIsEnumerable.

var x = {
    name : "Garrett"
    ,bench: 365
    ,height : 73
    ,weight : 215
    ,toString : function() {
        return "not strong enough, not lean enough.";
    }
};
var s = "";
// For any value of 's', this must always be true.
var alwaysTrueExceptIE = g.hasOwnProperty( s ) == g.propertyIsEnumerable( s );

Not many authors use propertyIsEnumerable. I believe that it hasn't gotten widespread usage for two reasons:

Not widely supported
Mac IE never supported it. It wasn't supported in Safari until Safari 3.
Not very useful
The specifification doesn't provide checking the prototype chain.

There aren't many other uses of propertyIsEnumerable in production code (excepting the anomalous YUI connection manager). Changing propertyIsEnumerable in the spec appears safe.

The other proposed change to propertyIsEnumerable is to have a second parameter to accept a boolean to set the DontEnum attribute. Although access to the DontEnum attribute is necessary, the approach separates properties from property attributes. Attributes are like modifiers.

Fix propertyIsEnumerable

The method propertyIsEnumerable should be renamed to isPropertyEnumerable, just like it has been in Flash.

This method should check the prototype chain. This would allow for four possibilities of r[ p ], when used with hasOwnProperty

Patch propertyIsEnumerable?

The propertyIsEnumerable Challenge

I leave a challenge to the reader to solve this problem cross browser:

Replace Object.prototype.propertyIsEnumerable when Safari 2 becomes nearly obsolete.

Since Safari 2 does not have Object.prototype.propertyIsEnumerable; adding it to Object.prototype would cause it to be enumerated by a for in loop. This would be disastrous.

There are a few approaches that we could consider:

  1. Check to see if it's a native function. Property values that are native code are usually values of properties that are DontEnum. Property values that are DontEnum are usually native code.
  2. Check the type of object and based on it's type, conditionally check the value. If the spec says the value is DontEnum, return false. Otherwise, return true.
  3. Not use propertyIsEnumerable. Not surprisingly, this is what most programmers do.

Checking to see if it's a native function won't solve the problem. Here's why:

  1. Replacing the value of the property does not change the property attributes. See the toFixed example for more details. This is clearly explained in ECMAScript's internal [[put]] method (§8.6.2.2).
  2. In fact, a Function's toString can be user-defined. This is a perfectly valid use-case. The effect this has is that a non-native function's toString might be invalid code. For example:
    function Widget(){}
    Widget.toString = function() {
        return "[object Widget]";
    };
        	

    Don't rely on toString

  3. When a native method has been added to the object by the programmer. For example: myObj.sort = Array.prototype.sort would result in myObj having an enumerable sort property. While uncommon, it is perfectly valid to borrow Array's generic sort method.
/** Native code is never returned; instead, we always 
 * get a string that could never be compilable, and would 
 * cause a parser error. 
 */
Function.isNative = function(f) {
    if(typeof f != "function") return false;
    try {
        new Function( f.toString() );
    }
    catch(syntaxError) { 
        return true;
    }
    return false;
};

This might seem useful, but even if it were reliable (it isn't), it would not return a property's DontEnum flag.

Host objects may also implement the DontEnum flag.

Next in this tutorial: