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

Chapter 16. JavaScript Objects

16.0. Introduction

Your JavaScript applications can consist entirely of functions and variables—both local and global—but if you want to ensure ease of reuse, compactness of code, and efficiency, as well as code that plays well with other libraries, you’re going to need to consider opportunities to encapsulate your code into objects.

Luckily, working with objects in JavaScript isn’t much more complicated than working with functions. After all, a JavaScript function is an object, and all objects are, technically, just functions.

Confused yet?

Unlike languages such as Java or C++, which are based on classes and class instances, JavaScript is based on prototypal inheritance. What prototypal inheritance means is that reuse occurs through creating new instances of existing objects, rather than instances of a class. Instead of extensibility occurring through class inheritance, prototypal extensibility happens by enhancing an existing object with new properties and methods.

Prototype-based languages have an advantage in that you don’t have to worry about creating the classes first, and then the applications. You can focus on creating applications, and then deriving the object framework via the effort.

It sounds like a mishmash concept, but hopefully as you walk through the recipes you’ll get a better feel for JavaScript’s prototype-based, object-oriented capabilities.

Note

ECMAScript and all its variations, including JavaScript, isn’t the only prototype-based language. The Wikipedia page on prototype-based languages, lists several.

See Also

Several of the recipes in this book are based on new functionality that was introduced in ECMAScript 5. You can access the complete ECMAScript 5 specification (a PDF) at http://www.ecmascript.org/docs/tc39-2009-043.pdf.

Note that the implementation of the new functionality is sketchy. I’ll point out browser support as I go.

16.1. Defining a Basic JavaScript Object

Problem

You want to create a custom, reusable JavaScript object.

Solution

Define a new object explicitly using functional syntax, and then create new instances, passing in the data the object constructor is expecting:

function Tune (song, artist) {
   this.title = song;
   this.artist = artist;
   this.concat=function() {
      return this.title + "-" + this.artist;
   }
}

window.onload=function() {
   var happySong = new Array();
   happySong[0] = new Tune("Putting on the Ritz", "Ella Fitzgerald");

   // print out title and artist
   alert(happySong[0].concat());
}

Discussion

As you can see from the solution, there is no class description, as you might expect if you’ve used other languages. A new object is created as a function, with three members: two properties, title and artist, and one method, concat. You could even use it like a function:

Tune("test","artist");

However, using the object like a function, as compared to using it as an object constructor, has an odd and definitely unexpected consequence.

The new keyword is used to create a new Tune instance. Values are passed into the object constructor, which are then assigned to the Tune properties, title and artist, via the this keyword. In the solution, this is a reference to the object instance. When you assign the property values to the object instance, you can then access them at a later time, using syntax like happySong[0].title. In addition, the Tune object’s concat method also has access to these properties.

However, when you use Tune like a regular function, this doesn’t represent an object instance, because there isn’t any. Instead, this references the owner of the Tune function, which in this case, is the global window object:

// treating Tune like a function
Tune("the title", "the singer");
alert(window.concat()); // lo and behold,
                        // "the title the singer" prints out

Completely unexpected and unwelcome behavior.

To summarize: to create a new object type, you can create a function with both properties and methods. To ensure the properties are assigned to the correct object, treat the object like a constructor using the new operator, rather than as a function. The this keyword establishes ownership of the properties, which is the Tune object instance, if the function is used as an object constructor and not a regular function.

See Also

See Recipe 16.2 for more information about the role this plays with JavaScript objects.

16.2. Keeping Object Members Private

Problem

You want to keep one or more object properties private, so they can’t be accessed outside the object instance.

Solution

When creating the private data members, do not use the this keyword with the member:

function Tune(song,artist) {
   var title = song;
   var artist = artist;
   this.concat = function() {
      return title + " " + artist;
   }
}

window.onload=function() {

   var happySongs = new Array();
  happySongs[0] = new Tune("Putting on the Ritz", "Ella Fitzgerald");

   try {

     // error
     alert(happySongs[0].title);
   } catch(e) {
     alert(e);
   }

   // prints out correct title and artist
   alert(happySongs[0].concat());
}

Discussion

Members in the object constructor (the function body), are not accessible outside the object unless they’re assigned to that object using this. If they’re attached to the object using the var keyword, only the Tune’s inner function, the concat method, can access these now-private data members.

This type of method—one that can access the private data members, but is, itself, exposed to public access via this—has been termed a privileged method by Douglas Crockford, the father of JSON (JavaScript Object Notation). As he himself explains (at http://www.crockford.com/javascript/private.html):

This pattern of public, private, and privileged members is possible because JavaScript has closures. What this means is that an inner function always has access to the vars and parameters of its outer function, even after the outer function has returned. This is an extremely powerful property of the language [. . . .] Private and privileged members can only be made when an object is constructed. Public members can be added at any time.

See Also

See Recipe 6.5 for more on function closures. See Recipe 16.3 for more on adding public members after the object has been defined.

16.3. Expanding Objects with prototype

You want to extend an existing object with a new method.

Solution

Use the Object prototype to extend the object:

Tune.prototype.addCategory = function(categoryName) {
   this.category = categoryName;
}

Discussion

Every object in JavaScript inherits from Object, and all methods and other properties are inherited via the prototype object. It’s through the prototype object that we can extend any object, and that includes the built-in objects, such as String and Number. Once an object has been extended via the prototype property, all instances of the object within the scope of the application have this functionality.

In Example 16-1, the new object, Tune, is defined using function syntax. It has two private data members, a title and an artist. A publicly accessible method, concat, takes these two private data members, concatenates them, and returns the result.

After a new instance of the object is created, and the object is extended with a new method and data member, the new method is used to update the existing object instance.

Example 16-1. Instantiating a new object, adding values, and extending the object
<!DOCTYPE html>
<head>
<title>Tune Object</title>
<script>


  function Tune(song,artist) {
    var title = song;
    var artist = artist;
    this.concat = function() {
      return title + " " + artist;
    }
  }

window.onload=function() {
  // create instance, print out values
  var happySong = new Tune("Putting on the Ritz", "Ella Fitzgerald");

  // extend the object
  Tune.prototype.addCategory = function(categoryName) {
      this.category = categoryName;
  }

  // add category
  happySong.addCategory("Swing");

  // print song out to new paragraph
  var song = "Title and artist: " + happySong.concat() +
             " Category: " + happySong.category;
  var p = document.createElement("p");
  var txt = document.createTextNode(song);
  p.appendChild(txt);
  document.getElementById("song").appendChild(p);
}

</script>

</head>
<body>
<h1>Tune</h1>
<div id="song">
</div>
</body>
</html>

Figure 16-1 shows the page after the new element and data have been appended.

Demonstration of custom JavaScript object after values printed out
Figure 16-1. Demonstration of custom JavaScript object after values printed out

The prototype property can also be used to override or extend external or global objects. Before ECMAScript 5 added trim as a default method for the String object, applications used to extend the String object by adding a trim method through the prototype object:

String.prototype.trim = function() {
  return (this.replace(/^[\s\xA0]+/, "").replace(/[\s\xA0]+$/, ""));
}

Needless to say, you’d want to use extreme caution when using this functionality with global objects. Applications that have extended the String object with a homegrown trim method may end up behaving differently than applications using the new standard trim method.

Using prototype with your own objects is usually safe. The only time you may run into problems is if you provide your objects as an external library, and others build on them.

16.4. Adding Getter/Setter to Objects

Problem

You want to provide property access to protected data.

Solution

Use the getter/setter functionality introduced with ECMAScript 3.1 with your (or outside) objects:

function Tune() {
  var artist;
  var song;

  this.__defineGetter__("artist",function() {
      return artist});

  this.__defineSetter__("artist",function(val) {
      artist = "By: " + val});

  this.__defineGetter__("song",function() {
      return "Song: " + song});

  this.__defineSetter__("song",function(val) {
      song=val});
}

window.onload=function() {
  var happySong = new Tune();

  happySong.artist="Ella Fitzgerald";
  happySong.song="Putting on the Ritz";

  alert(happySong.song + " " + happySong.artist);
}

Discussion

We can add functions to our objects to get private data, as demonstrated in Recipe 16.2. However, when we use the functions, they are obvious functions. The getter/setter functionality is a special syntax that provides property-like access to private data members. The getter and setter functions provide an extra layer of protection for the private data members. The functions also allow us to prepare the data, as demonstrated with the solution, where the song and artist strings are concatenated to labels.

You can define the getter and setter functions within the object constructor, as shown in the solution:

this.__defineGetter__("song",function() {
      return "Song: " + song});

this.__defineSetter__("song",function(val) {
      song=val});

You can also add a getter/setter with other objects, including DOM objects. To add a getter/setter outside of the object constructor, you first need to access the object’s prototype object, and then add the getter/setter functions to the prototype:

var p = Tune.prototype;

p.__defineGetter__("song",function() {
    return "Song: " + title});

p.__defineSetter__("song",function(val) {
    title=val});

You can also use getter/setters with “one-off” objects, used to provide JavaScript namespacing (covered later in the chapter):

var Book = {
   title: "The JavaScript Cookbook",
   get booktitle() {
      return this.title;
   },
   set booktitle(val) {
      this.title = val;
   }
};

Book.booktitle = "Learning JavaScript";

This approach can’t be used to hide the data, but can be used to control the display of the data, or to provide special processing of the incoming data before it’s stored in the data member.

Note

The getter/setter functionality does not work in IE8 or earlier.

See Also

See Recipe 16.6 for demonstrations of the new ECMAScript 5 property methods. Recipe 16.11 covers JavaScript namespacing.

16.5. Inheriting an Object’s Functionality

Problem

When creating a new object type, you want to inherit the functionality of an existing JavaScript object.

Solution

Use the concept of constructor chaining and the Function.apply method to emulate traditional class inheritance behavior in JavaScript:

function oldObject(param1) {
   this.param1 = param1;
   this.getParam=function() {
      return this.param1;
   }
}

function newObject(param1,param2) {
   this.param2 = param2;
   this.getParam2 = function() {
      return this.param2;
   }
   oldObject.apply(this,arguments);
   this.getAllParameters=function() {
      return this.getParam() + " " + this.getParam2();
   }
}

window.onload=function() {
   newObject.prototype = new oldObject();
   var obj = new newObject("value1","value2");

   // prints out both parameters
   alert(obj.getAllParameters());
}

Discussion

In the solution, we have two objects: the original, oldObject, and newObject that inherits functionality from the older object. For this to work, a couple of things are happening.

First, in the constructor for newObject, an apply method is called on oldObject, passing in a reference to the new object, and the argument array. The apply method is inherited from the Function object, and takes a reference to the calling object, or the window object if the value is null, and an optional argument array.

The second part of the inheritance is the line:

newObject.prototype=new oldObject();

This is an example of constructor chaining in JavaScript. What happens is when you create a new instance of newObject, you’re also creating a new instance of oldObject, in such a way that newObject inherits both the older object’s methods and property.

It is this combination of constructor chaining, which chains the constructors of the new objects together, and the apply method, which passes both object context and data to the inherited object, that implements inheritance behavior in JavaScript. Because of this inheritance, the new object has access not only to its own property, param2, and method, getParam2, but also has access to the old object’s param1 and getParam property and method.

To see another example of JavaScript inheritance, Example 16-2 shows it working with another couple of objects: a Book object and a TechBook object, which inherits from Book. The lines that implement the inheritance are bolded.

Example 16-2. Demonstrating object inheritance in JavaScript
<!DOCTYPE html>
<head>
<title>Constructor Chaining</title>
<script type="text/javascript">

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

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() {

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

  // get all values
  var newBook = new TechBook("The JavaScript Cookbook",
  "Shelley Powers", "Programming");
  alert(newBook.getBook());

  // now, individually
  alert(newBook.getTitle());
  alert(newBook.getAuthor());
  alert(newBook.getCategory());
}

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

Unlike the objects in the solution, all of the data members in both objects in Example 16-2 are protected, which means the data can’t be directly accessed outside the objects. Yet notice when all three Book properties—title, author, and category—are printed out via the getBook method in the TechBook object, that TechBook has access to the Book author and title properties, in addition to its own category. That’s because when the new TechBook object was created, a new Book object was also created and inherited by the new TechBook object. To complete the inheritance, the data used in the constructor for TechBook is passed through the Book object using the apply method.

Not only can the TechBook object directly access the Book instance data, you can access both of the Book object’s methods, getAuthor and getCategory, directly on the instantiated TechBook object instance.

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.

16.7. Enumerating an Object’s Properties

Problem

You want to see what properties an object has.

Solution

Use a specialized variation of the for loop to iterate through the properties:

for (var prop in obj) {
   alert(prop); // prints out the property name
}

or use the new ECMAScript 5 Object.keys method to return the names for all properties that can be enumerated:

alert(Object.keys(obj).join(", "));

or use another new ECMAScript 5 method, Object.getOwnPropertyNames(obj), to get the names of all properties, whether they can be enumerated or not:

var props = Object.getOwnPropertyNames(obj);

Discussion

For an object instance, newBook, based on the following object definitions:

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

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

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

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

using the for...in loop:

var str = "";
for (var prop in newBook) {
   str = str + prop + " ";
}

alert(str);

a message pops up with the following values:

getCategory author getTitle getAuthor getBook

Neither the category property in TechBook nor the title property in Book are returned, as these are private data members. When using WebKit nightly or Firefox Minefield, the same result is returned when using the new Object.keys method:

alert(Object.keys(newBook).join(" "));

The same result is also returned, again with WebKit nightly or Firefox Minefield, when using the new Object.getOwnPropertyNames method:

var props = Object.getOwnPropertyNames(newBook);
alert(props.join(" "));

However, if I add a property descriptor for the title property, making it enumerable:

// test data descriptor
Object.defineProperty(newBook, "title", {
       writable: true,
       enumerable: true,
       configurable: true});

When I enumerate over the properties again, this time the title displays among the properties, even though it still can’t be accessed or reset directly on the object.

We can also use these same property enumerators over the object constructors (Book or TechBook) or any of the built-in objects. However, the values we get back when we enumerate over Book, as compared to the instance, newBook, do vary among the methods. The for...in loop returns just one property, prototype, as does the Object.keys method. That’s because prototype is the only enumerable property for the Function object. While newBook is an instance of Book, Book, itself is an instance of the Function object.

The Object.getOwnPropertyNames, however, returns the following set of properties for Book:

arguments callee caller length name prototype

Unlike Object.keys and the for...in loop, Object.getOwnPropertyNames returns a list of all properties, not just those that are enumerable. This leads to a new question: why did Object.getOwnPropertyNames not return all of the properties for the newBook instance? It should have picked up title before it was made enumerable, as well as TechBook’s private member, category.

I added another property descriptor to newBook, this time for category, and this time with enumerable set to false:

Object.defineProperty(newBook,"category", {
       writable: true,
       enumerable: false,
       configurable: true});

The category property isn’t listed when I use for...in or Object.keys, but this time it is picked up by Object.getOwnPropertyNames.

It would seem that a property must either be publicly accessible, or have a property descriptor for it to be picked up by Object.getOwnPropertyNames, at least for these earlier implementations of these new Object methods. I imagine the reason is to ensure consistent results between older ECMAScript implementations and newer ones: defining a property in older versions of ECMAScript is not the same as defining a property in the newer version.

Speaking of ECMAScript 5 and new Object methods, support for the older for...in loop is broad, but support for Object.keys and Object.getOwnPropertyNames, in addition to support for property descriptors, is sparse at this time. Opera does not support defineProperty and the associated new ECMAScript 5 functionality. WebKit nightly and the Chrome beta support all of the new functionality, while the Firefox nightly (Minefield), supports Object.keys, but not getOwnPropertyNames. IE8’s coverage is limited because it only supports the new methods on DOM elements, such as Image, and not on custom objects.

See Also

For more on property descriptors, see Recipe 16.6.

16.8. Preventing Object Extensibility

Problem

You want to prevent others from extending an object.

Solution

Use the new ECMAScript 5 Object.preventExtensions method to lock an object against future property additions:

"use strict";

var Test = {
    value1 : "one",
    value2 : function() {
      return this.value1;
    }
};

try {
   Object.preventExtensions(Test);

   // the following fails, and throws an exception in Strict mode
  Test.value3 = "test";

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

Discussion

Covering Object.preventExtensions is a leap of faith on my part, as no browser has implemented this new ECMAScript 5 functionality. However, by the time this book hits the streets, I expect (hope) at least a couple of browsers will have implemented this new feature.

The Object.preventExtensions method prevents developers from extending the object with new properties, though property values themselves are still writable. It sets an internal property, Extensible, to false. You can check to see if an object is extensible using Object.isExtensible:

if (Object.isExtensible(obj)) {
   // extend the object
}

Though you can’t extend the object, you can edit existing property values, as well as modify the object’s property descriptor.

See Also

Recipe 16.6 covers property descriptors.

16.9. Preventing Object Additions and Changes to Property Descriptors

Problem

You want to prevent extensions to an object, but you also want to disallow someone from changing the property descriptor for an object.

Solution

Use the new ECMAScript Object.seal method to seal the object against additions and modification of its property descriptor:

"use strict";

 var Test = {
    value1 : "one",
    value2 : function() {
      return this.value1;
    }
  }

  try {
     // freeze the object
     Object.seal(Test);

     // the following would succeed
     Test.value2 = "two";

     // the following would fail, throw an error in Strict Mode
     Test.newProp = "value3";

      // so would the following
      Object.defineProperty(Title, "category", {
         get: function () { return category; },
         set: function (value) { category = value; },
         enumerable: true,
         configurable: true});
   } catch(e) {
      alert(e);
   }

Discussion

Like Object.preventExtension, covered in Recipe 16.8, the Object.seal method is another new ECMAScript 5 method that has no browser implementation yet, but should, knock on wood, by the time you read this book. Look for a first implementation in a Safari nightly build or a Firefox Minefield build.

The Object.seal method prevents extensions to an object, like Object.preventExtensions, but also prevents any changes to the object’s property descriptor. To check if an object is sealed, you would use the Object.isSealed method:

if (Object.isSealed(obj)) ...

See Also

Property descriptors are described in Recipe 16.6, and Object.preventExtensions is covered in Recipe 16.8.

16.10. Preventing Any Changes to an Object

Problem

You’ve defined your object, and now you want to make sure that its properties aren’t redefined or edited by other applications using the object.

Solution

Use the new ECMAScript 5 Object.freeze method to freeze the object against any and all changes:

"use strict";
  var Test = {
    value1 : "one",
    value2 : function() {
      return this.value1;
    }
  }

try {
  // freeze the object
  Object.freeze(Test);

  // the following would throw an error in Strict Mode
  Test.value2 = "two";

   // so would the following
   Test.newProperty = "value";

   // and so would the following
   Object.defineProperty(Title, "category", {
      get: function () { return category; },
      set: function (value) { category = value; },
      enumerable: true,
      configurable: true});
} catch(e) {
   alert(e);
}

Discussion

There are several new Object methods defined in ECMAScript 5 to provide better object management in JavaScript. The least restrictive is Object.preventExtensions(obj), covered in Recipe 16.6, which disallows adding new properties to an object, but you can change the object’s property descriptor, or modify an existing property value.

The next more restrictive method is Object.seal. The Object.seal(obj) method prevents any modifications or new properties from being added to the property descriptor, but you can modify an existing property value.

The most restrictive new ECMAScript 5 Object method is Object.freeze. The Object.freeze(obj) method disallows extensions to the object, and restricts changes to the property descriptor. However, Object.freeze also prevents any and all edits to existing object properties. Literally, once the object is frozen, that’s it—no additions, no changes to existing properties.

You can check to see if an object is frozen using the companion method, Object.isFrozen:

if (Object.isFrozen(obj)) ...

No browser currently implements Object.freeze or Object.isFrozen, but this state should change relatively soon.

See Also

Recipe 16.6 covers property descriptors, Recipe 16.8 covers Object.preventExtensions, and Recipe 16.9 covers Object.seal.

16.11. One-Off Objects and Namespacing Your JavaScript

Problem

You want to encapsulate your library functionality in such a way as to prevent clashes with other libraries.

Solution

Use an object literal, what I call a one-off object, to implement the JavaScript version of namespacing. An example is the following:

var jscbObject = {

   // return element
   getElem : function (identifier) {
      return document.getElementById(identifier);
   },

   stripslashes : function(str) {
      return str.replace(/\\/g, '');
   },

   removeAngleBrackets: function(str) {
      return str.replace(/</g,'&lt;').replace(/>/g,'&gt;');
   }
};

var incoming = jscbObject.getElem("incoming");
var content = incoming.innerHTML;

var result = jscbObject.stripslashes(content);
result = jscbObject.removeAngleBrackets(result);

jscbObject.getElem("result").innerHTML=result;

Discussion

As mentioned elsewhere in this book, all built-in objects in JavaScript have a literal representation in addition to their more formal object representation. For instance, an Array can be created as follows:

var newArray = new Array('one','two','three');

or using the array literal notation:

var newArray = ['one','two','three'];

The same is true for objects. The notation for object literals is pairs of property names and associated values, separated by commas, and wrapped in curly brackets:

var newObj = {
    prop1 : "value",
    prop2 : function() { ... },
    ...
};

The property/value pairs are separated by colons. The properties can be scalar data values, or they can be functions. The object members can then be accessed using the object dot-notation:

var tmp = newObj.prop2();

Or:

var val = newObj.prop1 * 20;

Or:

getElem("result").innerHTML=result;

Using an object literal, we can wrap all of our library’s functionality in such a way that the functions and variables we need aren’t in the global space. The only global object is the actual object literal, and if we use a name that incorporates functionality, group, purpose, author, and so on, in a unique manner, we effectively namespace the functionality, preventing name clashes with other libraries.

This is the approach I use for every library, whether I create the library or use another, such as jQuery, Dojo, Prototype, and so on. As we’ll see later in the book, object literal notation is also the notation used by JSON, which is now formally a part of the ECMAScript 5 specification.

See Also

See Recipe 17.1 for a discussion related to packaging your code into a library for external distribution. Also check out Chapter 19 for recipes related to JSON.

16.12. Rediscovering “this” with Prototype.bind

Problem

You want to control the scope assigned a given function.

Solution

Use the new ECMAScript 5 function bind method:

window.onload=function() {

   window.name = "window";

   var newObject = {
      name: "object",

      sayGreeting: function() {
            alert("Now this is easy, " + this.name);
            nestedGreeting = function(greeting) {
              alert(greeting + " " + this.name);
            }.bind(this);

            nestedGreeting("hello");
       }
   }

   newObject.sayGreeting("hello");
}

If the method isn’t supported in your target browser(s), extend the Function object with the code popularized by the Prototype.js JavaScript library:

Function.prototype.bind = function(scope) {
  var _function = this;

  return function() {
    return _function.apply(scope, arguments);
  }
}

Discussion

The this keyword represents the owner or scope of the function. The challenge associated with this in current JavaScript libraries is that we can’t guarantee which scope applies to a function.

In the solution, the literal object has a method, sayGreeting, which prints a message out using an alert, and then maps another nested function to its property, nestedGreeting.

Without the Function.bind method, the first message printed out would say, “Now this is easy object”, but the second would say, “hello window”. The reason the second printout references a different name is that the nesting of the function disassociates the inner function from the surrounding object, and all unscoped functions automatically become the property of the window object.

What the bind method does is use the apply method to bind the function to the object passed to the object. In the example, the bind method is invoked on the nested function, binding it with the parent object using the apply method.

bind is particularly useful for timers, such as setInterval. Example 16-4 is a web page with script that uses setTimeout to perform a countdown operation, from 10 to 0. As the numbers are counted down, they’re inserted into the web page using the element’s innerHTML property. Since most browsers have not implemented Function.bind as a standard method yet, I added the Function.prototype.bind functional code.

Example 16-4. Demonstration of the utility of Function.bind for timers
<!DOCTYPE html>
<head>
<title>Using bind with timers</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
#item { font-size: 72pt; margin: 70px auto;
        width: 100px;}
</style>

<script>

if (!Function.bind) {
   Function.prototype.bind = function(scope) {
      var _function = this;

      return function() {
        return _function.apply(scope, arguments);
      }
   }
}

window.onload=function() {
   var theCounter = new Counter('item',10,0);
   theCounter.countDown();
}

function Counter(id,start,finish) {
   this.count = this.start = start;
   this.finish = finish;
   this.id = id;
   this.countDown = function() {
      if (this.count == this.finish) {
         this.countDown=null;
         return;
      }
      document.getElementById(this.id).innerHTML=this.count--;
      setTimeout(this.countDown.bind(this),1000);
   };

}
</script>
</head>
<body>
<div id="item">
10
</div>
</body>

If the line in bold text in the code sample had been the following:

setTimeout(this.countDown, 1000);

The application wouldn’t have worked, because the object scope and counter would have been lost when the method was invoked in the timer.

16.13. Chaining Your Object’s Methods

Problem

You wish to define your object’s methods in such a way that more than one can be used at the same time, like the following, which retrieves a reference to a page element and sets the element’s style property:

document.getElementById("elem").setAttribute("class","buttondiv");

Solution

The ability to directly call one function on the result of another in the same line of code is known as method chaining. It requires specialized code in whatever method you want to chain.

For instance, if you want to be able to chain the changeAuthor method in the following object, you must also return the object after you perform whatever other functionality you need:

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

  this.getAuthor=function() {
     return "Author: " + author;
  }

  this.replaceTitle = function (newTitle) {
    var oldTitle = title;
    title = newTitle;
  }

  this.replaceAuthor = function(newAuthor) {
    var oldAuthor = author;
    author = newAuthor;
  }
}

function TechBook (title, author, category) {

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

   Book.apply(this,arguments);
   this.changeAuthor = function(newAuthor) {
     this.replaceAuthor(newAuthor);
     return this;
   }
}

window.onload=function() {

try {

 var newBook = new TechBook("I Know Things", "Shelley Powers", "tech");
 alert(newBook.changeAuthor("Book K. Reader").getAuthor());

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

}

Discussion

The key to making method chaining work is to return a reference to the object at the end of the method, as shown in the replaceAuthor method in the solution:

this.changeAuthor = function(newAuthor) {
  this.replaceAuthor(newAuthor);
  return this;
}

In this example, the line return this returns the object reference.

Chaining is extensively used within the DOM methods, as shown throughout this book, when we see functionality such as:

var result = str.replace(/</g,'&lt;').replace(/>/g,'&gt;');

Libraries such as jQuery also make extensive use of method chaining:

$(document).ready(function() {
   $("#orderedlist").find("li").each(function(i) {
     $(this).append( " BAM! " + i );
   });
 });

If you examine the development version of jQuery (which is uncompressed and very readable), you’ll see return this sprinkled all throughout the library’s methods:

// Force the current matched set of elements to become
// the specified array of elements
// (destroying the stack in the process)
// You should use pushStack() in order to do this,
// but maintain the stack

setArray: function( elems ) {
   // Resetting the length to 0, then using the native Array push
   // is a super-fast way to populate an object with
   // array-like properties
   this.length = 0;
   push.apply( this, elems );

   return this;
   },

See Also

Chapter 17 provides an introduction to using jQuery in your JavaScript applications.

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