HTML5 Zone is brought to you in partnership with:

Dr. Axel Rauschmayer is a freelance software engineer, blogger and educator, located in Munich, Germany. Axel is a DZone MVB and is not an employee of DZone and has posted 246 posts at DZone. You can read more from them at their website. View Full User Profile

JavaScript properties: inheritance and enumerability

07.18.2011
| 6617 views |
  • submit to reddit

This post examines how inheritance and enumerability affects operations on properties in JavaScript.

Kinds of properties

In principle, objects in JavaScript are simple maps (dictionaries) from strings to values. However, two factors make things more complicated:
  • Own versus inherited properties. An object can point to its prototype, via an internal property. The prototype chain is the sequence of objects that starts with an object, continues with its prototype, the prototype’s prototype, etc. Many operations consider all properties in a prototype chain, some operations only consider the own properties that are stored in the first object of the prototype chain.
  • Enumerable properties. You can hide properties from some operations by making them non-enumerable. Enumerability is one of the three attributes of a property: writability, enumerability, configurability [1].

Accessing properties

There are the following operations for accessing properties:
  1. Enumerable own properties.
    Get property names:
        Object.keys(obj)
    
  2. All own properties.
    Get/detect property names:
        Object.getOwnPropertyNames(obj)
        Object.hasOwnProperty(obj, propName)
    
    Get property value:
        Object.getOwnPropertyDescriptor(obj)
    
    Set property values, delete properties (only affects the first object in the prototype chain):
        obj.propName = value
        obj["propName"] = value
    
        delete obj.propName
        delete obj["propName"]
    
        Object.defineProperty(obj, propName, desc)
        Object.defineProperties(obj, descObj)
    
  3. Enumerable inherited properties.
    Get property names:
        for (propName in obj)
    
  4. All inherited properties.
    Detect property name:
        propName in obj
    
    Read property value:
        obj.propName
        obj["propName"]
    

Using an object as a map

Objects are frequently used as maps from strings to values or a sets of strings. One has to be careful when doing so: Almost every object has the prototype Object.prototype and thus inherits many properties:
    > "valueOf" in {}
    true
    > "toString" in {}
    true
With ECMAScript 5, you use the operations from (1) and (2) and everything is OK.
    > var proto = { foo: 123 };
    > var obj = Object.create(proto);
    > obj.hasOwnProperty("foo")
    false
However, prior to ECMAScript 5, people often used the operations from (3) and (4) and that causes problems:
    > for (var p in obj) console.log(p);
    foo
    > "foo" in obj
    true
If you make the prototype property non-enumerable, you can fix the for-in loop, but not the in operator:
    > var proto = {};
    > Object.defineProperty(proto, "foo", { enumerable: false, value: 123 });
    {}
    > var obj = Object.create(proto);
    > for (var p in obj) console.log(p);
    > "foo" in obj
    true

Enumerability and the standard library

In JavaScript, many properties are non-enumerable, especially all properties of prototypes. The only reason for this is to hide them from for-in. Let us examine what JavaScript hides by using the following two helper functions.
    /** Return an array with the names of the inherited enumerable properties of obj */
    function inheritedEnumerablePropertyNames(obj) {
        var result = [];
        for (var propName in obj) {
            result.push(propName);
        }
        return result;
    }

    /** Return an array with the names of the inherited properties of obj */
    function inheritedPropertyNames(obj) {
        if ((typeof obj) !== "object") { // null is not a problem
            throw new Error("Only objects are allowed");
        }
        var props = {};
        while(obj) {
            Object.getOwnPropertyNames(obj).forEach(function(p) {
                props[p] = true;
            });
            obj = Object.getPrototypeOf(obj);
        }
        return Object.getOwnPropertyNames(props);
    }
Objects: all non-own properties are non-enumerable.
    > inheritedPropertyNames({ foo: "abc" })
    [ 'foo',
      'constructor',
      'toString',
      ...
      '__lookupSetter__' ]
    > inheritedEnumerablePropertyNames({ foo: "abc" })
    [ 'foo' ]
Arrays: all non-own properties and length are non-enumerable.
    > inheritedPropertyNames([ "abc" ])
    [ '0',
      'length',
      'constructor',
      'concat',
      ...
      '__lookupSetter__' ]
    > inheritedEnumerablePropertyNames([ "abc" ])
    [ '0' ]
Note that this might give you the idea that you can use for-in to iterate over the indices in an array. However that is not recommended, because it won’t work properly if someone adds a (non-index) property to the array.

Best practices

The following recommendations involve ECMAScript 5 methods. Use a shim to get these methods in older browsers [2].

JavaScript programmers:

  • If you use an object as a map, only work with own properties, e.g. via the ECMAScript 5 methods Object.getOwnPropertyNames() and Object.hasOwnProperty().
  • Iterating over objects and arrays: see [4].
API authors:
  • When adding properties to built-in prototypes [3], use Object.defineProperty() and similar methods to make them non-enumerable. That will give you some protection against breaking for-in loops in legacy code.
  • With your own types, you don’t have to be as careful, because you can expect new code to ignore inherited properties when using objects as maps.
Future:
  • ECMAScript.next will have a dedicated type for maps. We thus won’t have to (ab)use objects as maps, any more.

Related reading

  1. John Resig - ECMAScript 5 Objects and Properties
  2. es5-shim: use ECMAScript 5 in older browsers
  3. Everything is Permitted: Extending Built-ins” [video]. Talk by Andrew Dupont at JSConf 2011. Inspired this post. Thanks to Brendan Eich for the pointer.
  4. Iterating over arrays and objects in JavaScript

 

From http://www.2ality.com/2011/07/js-properties.html

Published at DZone with permission of Axel Rauschmayer, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)