O'Reilly logo

JavaScript Cookbook by Shelley Powers

Stay ahead with the world's most comprehensive technology and business learning platform.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, tutorials, and more.

Start Free Trial

No credit card required

16.6. Extending an Object by Defining a New Property

Problem

You want to extend an existing object by adding a new property, but without changing the object’s constructor function.

Solution

Use the new ECMAScript Object.defineProperty method to define one property:

Object.defineProperty(newBook, "publisher", {
       value: "O'Reilly",
       writable: false,
       enumerable: true,
       configurable: true});

Use the Object.defineProperties method to define more than one property:

Object.defineProperties(newBook, {
     "stock": {
        value: true,
        writable: true,
        enumerable: true,
     },
     "age": {
        value: "13 and up",
        writable: false
     }
   });

Discussion

Properties are handled differently in ECMAScript 5. Where before all you could do was assign a value, now you have greater control over how an object’s properties are managed. This greater control comes about through the provision for several new attributes that can be assigned to a property when it’s created. These new attributes make up what is known as the property descriptor object, and include:

writable

If true, property can be changed; otherwise, not.

configurable

If true, property can be deleted, or changed; otherwise, not.

enumerable

If true, property can be iterated.

The type of property descriptor can also vary. If the descriptor is a data descriptor, another attribute is value, demonstrated in the solution and equivalent to the following:

someObject.newProperty = "somevalue";

If the descriptor is an accessor descriptor, the property has a getter/setter, similar to what was covered in Recipe 16.4. A restriction when defining an accessor property is that you can’t set the writable attribute:

 Object.defineProperty(TechBook, "category", {
    get: function () { return category; },
    set: function (value) { category = value; },
    enumerable: true,
    configurable: true});
var newBook = new TechBook(...);
newBook.publisher="O'Reilly";

You can also discover information about the property descriptor for a property with the Object.getOwnPropertyDescription method. To use, pass in the object and the property name whose property descriptor you wish to review:

var propDesc = Object.getOwnPropertyDescriptor(newBook,"category");
alert(propDesc.writable); // true if writable, otherwise false

The property has to be publicly accessible (not a private member). Easily view all of the property attributes using the JSON object’s stringify method:

var val = Object.getOwnPropertyDescriptor(TechBook,"category");
alert(JSON.stringify(val)); // {"enumerable":true,"configurable":true}

If the property descriptor configurable attribute is set to true, you can change descriptor attributes. For instance, to change the writable attribute from false to true, use the following:

Object.defineProperty(newBook, "publisher", {
   writable: true});

The previously set attributes retain their existing values.

Example 16-3 demonstrates the new property descriptors, first on a DOM object, then a custom object.

Example 16-3. Trying out new object property methods

<!DOCTYPE html>
<head>
<title>Object Properties</title>
<script type="text/javascript">

// Book custom object
function Book (title, author) {
  var title = title;
  var author = author;
  this.getTitle=function() {
     return "Title: " + title;
  }
  this.getAuthor=function() {
     return "Author: " + author;
  }
}

// TechBook, inheriting from Book
function TechBook (title, author, category) {

   var category = category;
   this.getCategory = function() {
     return "Technical Category: " + category;
   }

   Book.apply(this,arguments);
   this.getBook=function() {
     return this.getTitle() + " " + author + " " + this.getCategory();
   }
}
window.onload=function() {

   try {

      // DOM test, WebKit bites the dust on this one
      var img = new Image();

      // add new property and descriptor
      Object.defineProperty(img, "geolatitude", {
        get: function() { return geolatitude; },
        set: function(val) { geolatitude = val;},
        enumerable: true,
        configurable: true});

      // test configurable and enumerable attrs
      var props = "Image has ";
      for (var prop in img) {
        props+=prop + " ";
      }
      alert(props);

   } catch(e) {
      alert(e);
   }
  try {
     // now we lose IE8

     // chain the object constructors
     TechBook.prototype = new Book();

     // add new property and property descriptor
     Object.defineProperty(TechBook, "experience", {
       get: function () { return category; },
       set: function (value) { category = value; },
       enumerable: false,
       configurable: true});

     // get property descriptor and print
     var val = Object.getOwnPropertyDescriptor(TechBook,"experience");
     alert(JSON.stringify(val));

     // test configurable and enumerable
     props = "TechBook has ";
     for (var prop in TechBook) {
        props+=prop + " ";
     }
     alert(props);

     Object.defineProperty(TechBook, "experience", {
        enumerable: true});

     props = "TechBook now has ";
     for (var prop in TechBook) {
        props+=prop + " ";
     }
     alert(props);

     // create TechBook instance
     var newBook = new TechBook("The JavaScript Cookbook",
"Shelley Powers", "Programming");

     // test new setter
     newBook.experience="intermediate";

     // test data descriptor
     Object.defineProperty(newBook, "publisher", {
         value: "O'Reilly",
         writable: false,
         enumerable: true,
         configurable: true});

     // test writable
     newBook.publisher="Some Other";
     alert(newBook.publisher);

   } catch(e) {
      alert(e);
   }
}

</script>
</head>
<body>
<p>some content</p>
</body>

These methods are very new. At the time I wrote this recipe, they only work in nightly builds for WebKit and Firefox (Minefield), and in a very limited sense with IE8.

The IE8 limitation is that the new property methods only work with DOM elements. The Object.defineProperty method works with the Image element, but not with the custom objects. However, using defineProperty on DOM elements causes an exception in WebKit. None of the new property methods work with Opera. The Firefox Minefield nightly and the Chrome beta were the only browsers that currently work with both types of objects, as shown in Figure 16-2, which displays the Image object properties in Firefox.

Displaying Image properties after adding a new property with defineProperty

Figure 16-2. Displaying Image properties after adding a new property with defineProperty

After printing out the Image properties, a new property (experience) and property descriptor are added to the TechBook custom object. The Object.getOwnPropertyDescriptor is called, passing in the TechBook object and the property name, experience. The property descriptor object is returned, and the JSON.stringify method is used on the object to print out the values:

{"enumerable":false,"configurable:true}

Next, the property descriptor values are tested. Currently, because the experience property is not enumerable, the use of the for...in loop can’t enumerate through the properties, and the result is:

Techbook has prototype

The new experience property’s enumerable attribute is then changed to true, because the property descriptor for experience allows modification on descriptor values. Enumerating over the experience property now yields the following string for Firefox:

Techbook has prototype experience

However, Chrome does not pick up the prototype property. The next two lines of code create a new instance of the TechBook object and adds an experience, which is then printed out to demonstrate the success of the property.

The last code in the example adds another new property (publisher) and property descriptor. This is a data property descriptor, which can also take a default value: “O’Reilly”. The writable property descriptor is set to false, and configurable and enumerable descriptors are set to true. The code then tries to change the publisher value. However, the original publisher value of O'Reilly prints out because the publisher property writable attribute is set to false.

See Also

See Recipe 16.7 for more on property enumeration. Though not all browsers support defineProperty and defineProperties yet, there are workarounds, as detailed by John Resig in a nice article describing the new object property capabilities.

With Safari, you learn the way you learn best. Get unlimited access to videos, live online training, learning paths, books, interactive tutorials, and more.

Start Free Trial

No credit card required