O'Reilly logo

JavaScript Cookbook, 2nd Edition 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 4. The Malleable JavaScript Object

With the increased interest in functional programming, you might think there’s less interest in JavaScript’s object-based capability. However, JavaScript is a flexible, adaptable language, and is just as happy to embrace both functional programming and object-oriented development.

There is a caveat related to JavaScript’s object-oriented capabilities: unlike languages such as Java or C++, which are based on classes and class instances, JavaScript is based on prototypical inheritance. What prototypical 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, prototypical 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.

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;
  this.concat = function() {
    return title + " " + artist;
  }
}

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

console.log(happySongs[0].title); // undefined

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

Discussion

Variables in the object constructor (the function body), are not accessible outside the object unless they’re attached to that object using this. If they’re redefined using the var keyword or passed in as parameters 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:

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.

Be aware, though, that the privacy of the variable is somewhat illusory. One can easily assign a value to that property outside the constructor function, and overwrite the private data:

happySongs[0].title = 'testing';

console.log(happySongs[0].title); // testing

However, the “privacy” of the data isn’t meant to ensure security of the object. It’s a contract with the developer, a way of saying, “This data isn’t meant to be accessed directly, and doing so will probably mess up your application.” As such, developers also typically use a naming convention where private data members begin with an underscore, to highlight that they aren’t meant to be accessed or set directly:

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

See Also

See Creating a Function That Remembers Its State for more on function closures. See Using Prototype to Create Objects for more on adding public members after the object has been defined.

Using Prototype to Create Objects

You want to create a new object, but you don’t want to add all the properties and methods into the constructor function.

Solution

Use the object’s prototype to add the new properties:

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

Discussion

Object is the ancestor for every object in JavaScript; objects inherit methods and properties from the Object via the Object prototype. It’s through the prototype that we can add new methods to existing objects:

var str = 'one';

String.prototype.exclaim = function() {
  if (this.length == 0) return this;
  return this + '!';
}

var str2 = 'two';

console.log(str.exclaim()); // one!
console.log(str2.exclaim()); // two!

Before ECMAScript 5 added trim() to 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. 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. To avoid this, libraries test to see if the method already exists before adding their own.

We can also use prototype to add properties to our own objects. In Example 4-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, concatTitleArtist(), 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 (addCategory()) and data member (category) the new method is used to update the existing object instance.

Example 4-1. Instantiating a new object, adding values, and extending the object
function Tune(title,artist) {
  this.concatTitleArtist = function() {
    return title + " " + artist;
  }
}


// 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.concatTitleArtist() +
     " Category: " + happySong.category;

console.log(song);

The result of running the code is the following line printed out to the console:

"Title and artist: Putting on the Ritz Ella Fitzgerald Category: Swing"

One major advantage to extending an object using prototype is increased efficiency. When you add a method directly to a function constructor, such as the concat TitleArtist() method in Tune, every single instance of the object then has a copy of this function. Unlike the data members, the function isn’t unique to each object instance. When you extend the object using prototype, as the code did with addCategory(), the method is created on the object itself, and then shared equally between all instances of the objects.

Of course, using prototype also has disadvantages. Consider again the concat TitleArtist() method. It’s dependent on access to data members that are not accessible outside the object. If the concatTitleArtist() method was defined using prototype and then tried to access these data members, an error occurs.

If you define the method using prototype directly in the constructor function, it is created in the scope of the function and does have access to the private data, but the data is overridden each time a new object instance is created:

function Tune(title,artist) {
  var title = title;
  var artist = artist;
  Tune.prototype.concatTitleArtist = function() {
    return title + " " + artist;
   }
}

var sad = new Tune('Sad Song', 'Sad Singer')
var happy = new Tune('Happy', 'Happy Singer');

console.log(sad.concatTitleArtist()); // Happy Happy Singer

The only data unique to the prototype function is what’s available via this. There are twisty ways around this, but they not only add to the complexity of the application, they tend to undermine whatever efficiency we get using prototype.

Generally, if your function must deal with private data, it should be defined within the function constructor, and without using prototype. Otherwise, the data should be available via this, or static and never changing once the object is created.

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 Object.create() to implement the inheritance:

function origObject() {
  this.val1 = 'a';
  this.val2 = 'b';
}

origObject.prototype.returnVal1 = function() {
  return this.val1;
};

origObject.prototype.returnVal2 = function() {
  return this.val2;
};

function newObject() {
  this.val3 = 'c';
  origObject.call(this);
}

newObject.prototype = Object.create(origObject.prototype);
newObject.prototype.constructor=newObject;

newObject.prototype.getValues = function() {
  return this.val1 + " " + this.val2 + " "+ this.val3;
};

var obj = new newObject();

console.log(obj instanceof newObject); // true
console.log(obj instanceof origObject); // true

console.log(obj.getValues()); "a b c"

Discussion

The Object.create() method introduced with ECMAScript 5 provides classical inheritance in JavaScript. The first parameter is the object that serves as prototype for the newly created object, and the second optional parameter is a set of properties defined for the object, and equivalent to the second argument in Object.defineProperties().

In the solution for this recipe, the prototype for the original object is passed in the Object.create() call, assigned to the new object’s own prototype. The new object’s constructor property is set to the new object’s constructor function. The new object’s prototype is then extended with a new method, getValues(), which returns a string consisting of concatenated properties from both objects. Note the use of instanceof demonstrating how both the old and new object prototypes are in the new object’s prototype chain.

In the constructor function for the new object, you need to use call() to chain the constructors for both objects. If you want to pass the argument list between the two objects, use apply() instead, as demonstrated in Example 4-2.

Example 4-2. Demonstrating classical inheritance in JavaScript with Object.create
function Book (title, author) {
   this.getTitle=function() {
      return "Title: " + title;
   };
   this.getAuthor=function() {
      return "Author: " + author;
   };
}

function TechBook (title, author, category) {

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

   this.getBook=function() {
      return this.getTitle() + " " + author + " " + this.getCategory();
   };

  Book.apply(this, arguments);
}


TechBook.prototype = Object.create(Book.prototype);
TechBook.prototype.constructor = TechBook;

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

console.log(newBook.getBook());

// now, individually
console.log(newBook.getTitle());
console.log(newBook.getAuthor());
console.log(newBook.getCategory());

In jsBin, the output for the application is:

"Title: The JavaScript Cookbook Shelley Powers Technical Category: Programming"
"Title: The JavaScript Cookbook"
"Author: Shelley Powers"
"Technical Category: Programming"

Extending an Object by Defining a New Property

Problem

You can easily slap a new property onto an object, but you want to do so in such a way that you have more control of how it’s used.

Solution

Use the defineProperty() method to add the property.

Given the following object:

var data = {}

If you want to add the following two properties with the given characteristics:

  • type: Initial value set and can’t be changed, can’t be deleted or modified, but can be enumerated
  • id: Initial value set, but can be changed, can’t be deleted or modified, and can’t be enumerated

Use the following JavaScript:

var data = {};

Object.defineProperty(data, 'type', {
  value: 'primary',
  enumerable: true
});

console.log(data.type); // primary
data.type = 'secondary';
console.log(data.type); // nope, still primary

Object.defineProperty(data, 'id', {
  value: 1,
  writable: true
});

console.log(data.id); // 1
data.id=300;
console.log(data.id); // 300

for (prop in data) {
  console.log(prop); // only type displays
}

Discussion

The defineProperty() is a way of adding a property to an object other than direct assignment that gives us some control over its behavior and state. There are two variations of property you can create with defineProperty(): a data descriptor, as demonstrated in the solution, and an accessor descriptor, defined with a getter-setter function pair.

Note

The defineProperty() Object method for accessor descriptors replaces the now deprecated __defineGetter and __defineSetter.

An example of an accessor descriptor is the following:

var data = {};

var group = 'history';

Object.defineProperty(data, "category", {
  get: function () { return group; },
  set: function (value) { group = value; },
  enumerable: true,
  configurable: true
});

console.log(data.category); // history

group = 'math';
console.log(data.category); // math

data.category = 'spanish';
console.log(data.category); // spanish
console.log(group); // spanish

Changes to the value for data.category and group are now interconnected.

The Object.defineProperty() supports three parameters: the object, the property, and a descriptor object. The latter consists of the following options:

  • configurable: false by default; controls whether the property descriptor can be changed
  • enumerable: false by default; controls whether the property can be enumerated
  • writable: false by default; controls whether the property value can be changed through assignment
  • value: The initial value for the property
  • get: undefined by default; property getter
  • set: undefined by default; property setter

The defineProperty() method has wide support in all modern browsers, but with caveats. Safari does not allow its use on a DOM object, while IE8 only supports it on a DOM object (IE9 and later support it on all objects).

See Also

Preventing Object Extensibility details how to prevent the addition of new properties to an object, and Preventing Any Changes to an Object covers freezing an object against any further change.

Preventing Object Extensibility

Problem

You want to prevent others from extending an object.

Solution

Use the 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 a TypeError in Strict mode
  Test.value3 = "test";

} catch(e) {
   console.log(e);
}

Discussion

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
}

If you attempt to add a property to an object that can’t be extended, the effort will either fail silently, or, if strict mode is in effect, will throw a TypeError exception:

TypeError: Can't add property value3, object is not extensible

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

See Also

Extending an Object by Defining a New Property covers property descriptors. strict mode was covered in Extra: Speaking of Strict Mode.

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 Object.freeze() 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 throws an error in Strict Mode
  test.value2 = 'two';

  // so does the following
  test.newProperty = 'value';

  var val = 'test';

  // and the following
  Object.defineProperty(test, 'category', {
    get: function () { return test; },
    set: function (value) { test = value; },
    enumerable: true,
    configurable: true
  });
} catch(e) {
  console.log(e);
}

Discussion

ECMAScript 5 brought us several Object methods for better object management. The least restrictive is Object.preventExtensions(obj), covered in Preventing Object Extensibility, which disallows adding new properties to an object, but you can still change the object’s property descriptor or modify an existing property value.

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

The most restrictive method is Object.freeze(). This method disallows extensions to the object and restricts changes to the property descriptor. In addition, 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.

The first property modification in the solution code:

test.value2 = "two";

results in the following error (in Chrome):

TypeError: Cannot assign to read only property 'value2' of #<Object>

If we comment out the line, the next object adjustment:

test.newProperty = "value";

throws the following error:

TypeError: Can't add property newProperty, object is not extensible

Commenting out this line leaves the use of defineProperty():

var val = 'test';

// and the following
Object.defineProperty(test, "category", {
  get: function () { return test; },
  set: function (value) { test = value; },
  enumerable: true,
  configurable: true
});

We get the final exception, for the use of defineProperty() on the object:

TypeError: Cannot define property:category, object is not extensible.

If we’re not using strict mode, the first two assignments fail silently, but the use of defineProperty() still triggers an exception (this mixed result is another good reason for using strict mode).

Check if an object is frozen using the companion method, Object.isFrozen():

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

Namespacing Your JavaScript Objects

Problem

You want to encapsulate your data and functions 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 sample = "<div>testing\changes</div>";

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

console.log(result); //&lt;div&gt;testingchanges&lt;/div&gt;

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 individually 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.

Advanced

I use the term one-off with the object literal rather than the more commonly known singleton because, technically, the object literal doesn’t fit the singleton pattern.

A singleton pattern is one where only one instance of an object can be created. We can say this is true of our object literal, but there’s one big difference: a singleton can be instantiated at a specific time rather than exist as a static construct, which is what the solution defines.

I went to Addy Osmani’s JavaScript Design Patterns (O’Reilly) to get an example of a good implementation of a singleton:

var mySingleton = (function () {

  // Instance stores a reference to the Singleton
  var instance;

  function init() {

    // Singleton

    // Private methods and variables
    function privateMethod(){
        console.log( "I am private" );
    }

    var privateVariable = "Im also private";

    var privateRandomNumber = Math.random();

    return {

      // Public methods and variables
      publicMethod: function () {
        console.log( "The public can see me!" );
      },

      publicProperty: "I am also public",

      getRandomNumber: function() {
        return privateRandomNumber;
      }

    };

  };

  return {

    // Get the Singleton instance if one exists
    // or create one if it doesn't
    getInstance: function () {

      if ( !instance ) {
        instance = init();
      }

      return instance;
    }

  };

})();

singleA = mySingleton.getInstance();
var singleB = mySingleton.getInstance();
console.log( singleA.getRandomNumber() === singleB.getRandomNumber() );

The singleton uses an Immediately-Invoked Function Expression (IIFE) to wrap the object, which immediately returns an instance of the object. But not just any instance—if an instance already exists, it’s returned rather than a new instance. The latter is demonstrated by the object’s getRandomNumber() function, which returns a random number that is generated when the object is created, and returns the same random number regardless of which “instance” is accessed.

Note

Access Addy Osmani’s Learning JavaScript Design Patterns online, or you can purchase a digital and/or paper copy directly at O’Reilly, or from your favorite book seller.

See Also

Chapter 7 covers external libraries and packaging your code into a library for external distribution. Chapter 12 covers another important pattern, the module pattern, and how modularization works with JavaScript.

Rediscovering this with Prototype.bind

Problem

You want to control the scope assigned a given function.

Solution

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

Discussion

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

In the solution, the object has a method, sayGreeting(), which outputs a message and maps another nested function to its property, nestedGreeting.

Without the Function’s 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 4-3 is a web page with a 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.

Example 4-3. Demonstrating the utility of bind
<!DOCTYPE html>
<head>
<html>
<title>Using bind with timers</title>
<meta charset=utf-8" />
<style type="text/css">
  #item {
    font-size: 72pt;
    margin: 70px auto;
    width: 100px;
  }
</style>
</head>
<body>
  <div id="item">
    10
  </div>
  <script>

    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>

</body>
</html>

If the setTimeout() function 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.

Extra: self = this

An alternative to using bind(), and one that is still in popular use, is to assign this to a variable in the outer function, which is then accessible to the inner. Typically this is assigned to a variable named that or self:

window.onload=function() {

  window.name = "window";

  var newObject = {
    name: "object",

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

      nestedGreeting("hello");
    }
  };

  newObject.sayGreeting("hello");
};

Without the assignment, the second message would reference “window”, not “object”.

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, similar to 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 TechBook.changeAuthor() method in the following code snippet, you must also return the object after you perform whatever other functionality you need:

function Book (title, 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) {
  this.getCategory = function() {
    return "Technical Category: " + category;
  };

  Book.apply(this,arguments);

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

    return this;  // necessary to enable method chaining
  };
}

var newBook = new TechBook("I Know Things", "Smart Author", "tech");
console.log(newBook.changeAuthor("Book K. Reader").getAuthor());

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 changeAuthor() method in the solution:

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

  return this;  // necessary to enable method chaining
};

Chaining is used extensively in JavaScript objects, and demonstrated 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, as we’ll see later in the book.

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