Chapter 6

Object-Oriented Programming

WHAT’S IN THIS CHAPTER?

  • Understanding object properties
  • Understanding and creating objects
  • Understanding inheritance

Object-oriented (OO) languages typically are identified through their use of classes to create multiple objects that have the same properties and methods. As mentioned previously, ECMAScript has no concept of classes, and therefore objects are different than in class-based languages.

ECMA-262 defines an object as an “unordered collection of properties each of which contains a primitive value, object, or function.” Strictly speaking, this means that an object is an array of values in no particular order. Each property or method is identified by a name that is mapped to a value. For this reason (and others yet to be discussed), it helps to think of ECMAScript objects as hash tables: nothing more than a grouping of name-value pairs where the value may be data or a function.

Each object is created based on a reference type, either one of the native types discussed in the previous chapter, or a developer-defined type.

UNDERSTANDING OBJECTS

As mentioned in the previous chapter, the simplest way to create a custom object is to create a new instance of Object and add properties and methods to it, as in this example:

image
var person = new Object();
person.name = "Nicholas";
person.age = 29;
person.job = "Software Engineer";
                   
person.sayName = function(){
    alert(this.name);
};

CreatingObjectsExample01.htm

This example creates an object called person that has three properties (name, age, and job) and one method (sayName()). The sayName() method displays the value of this.name, which resolves to person.name. Early JavaScript developers used this pattern frequently to create new objects. A few years later, object literals became the preferred pattern for creating such objects. The previous example can be rewritten using object literal notation as follows:

var person = {
    name: "Nicholas",
    age: 29,
    job: "Software Engineer",
 
    sayName: function(){
        alert(this.name);
    }
};

The person object in this example is equivalent to the person object in the prior example, with all the same properties and methods. These properties are all created with certain characteristics that define their behavior in JavaScript.

Types of Properties

ECMA-262 fifth edition describes characteristics of properties through the use of internal-only attributes. These attributes are defined by the specification for implementation in JavaScript engines, and as such, these attributes are not directly accessible in JavaScript. To indicate that an attribute is internal, surround the attribute name with two pairs of square brackets, such as [[Enumerable]]. Although ECMA-262 third edition had different definitions, this book refers only to the fifth edition descriptions.

There are two types of properties: data properties and accessor properties.

Data Properties

Data properties contain a single location for a data value. Values are read from and written to this location. Data properties have four attributes describing their behavior:

  • [[Configurable]] — Indicates if the property may be redefined by removing the property via delete, changing the property’s attributes, or changing the property into an accessor property. By default, this is true for all properties defined directly on an object, as in the previous example.
  • [[Enumerable]] — Indicates if the property will be returned in a for-in loop. By default, this is true for all properties defined directly on an object, as in the previous example.
  • [[Writable]] — Indicates if the property’s value can be changed. By default, this is true for all properties defined directly on an object, as in the previous example.
  • [[Value]] — Contains the actual data value for the property. This is the location from which the property’s value is read and the location to which new values are saved. The default value for this attribute is undefined.

When a property is explicitly added to an object as in the previous examples, [[Configurable]], [[Enumerable]], and [[Writable]] are all set to true while the [[Value]] attribute is set to the assigned value. For example:

var person = {
    name: "Nicholas"
};

Here, the property called name is created and a value of "Nicholas" is assigned. That means [[Value]] is set to "Nicholas", and any changes to that value are stored in this location.

To change any of the default property attributes, you must use the ECMAScript 5 Object.defineProperty() method. This method accepts three arguments: the object on which the property should be added or modified, the name of the property, and a descriptor object. The properties on the descriptor object match the attribute names: configurable, enumerable, writable, and value. You can set one or all of these values to change the corresponding attribute values. For example:

image
var person = {};
Object.defineProperty(person, "name", {
    writable: false,
    value: "Nicholas"
});
 
alert(person.name);    //"Nicholas"
person.name = "Greg";
alert(person.name);    //"Nicholas"

DataPropertiesExample01.htm

This example creates a property called name with a value of "Nicholas" that is read-only. The value of this property can’t be changed, and any attempts to assign a new value are ignored in nonstrict mode. In strict mode, an error is thrown when an attempt is made to change the value of a read-only property.

Similar rules apply to creating a nonconfigurable property. For example:

var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Nicholas"
});
 
alert(person.name);    //"Nicholas"
delete person.name;
alert(person.name);    //"Nicholas"

DataPropertiesExample02.htm

Here, setting configurable to false means that the property cannot be removed from the object. Calling delete on the property has no effect in nonstrict mode and throws an error in strict mode. Additionally, once a property has been defined as nonconfigurable, it cannot become configurable again. Any attempt to call Object.defineProperty() and change any attribute other than writable causes an error:

image
var person = {};
Object.defineProperty(person, "name", {
    configurable: false,
    value: "Nicholas"
});
 
//throws an error
Object.defineProperty(person, "name", {
    configurable: true,
    value: "Nicholas"
});

DataPropertiesExample03.htm

So although you can call Object.defineProperty() multiple times for the same property, there are limits once configurable has been set to false.

When you are using Object.defineProperty(), the values for configurable, enumerable, and writable default to false unless otherwise specified. In most cases, you likely won’t need the powerful options provided by Object.defineProperty(), but it’s important to understand the concepts to have a good understanding of JavaScript objects.

image

Internet Explorer 8 was the first version to implement Object.defineProperty(). Unfortunately, the implementation is extremely limited. This method can be used only on DOM objects and can create only accessor properties. It’s recommended you avoid using Object.defineProperty() in Internet Explorer 8 because of its incomplete implementation.

Accessor Properties

Accessor properties do not contain a data value. Instead, they contain a combination of a getter function and a setter function (though both are not necessary). When an accessor property is read from, the getter function is called, and it’s the function’s responsibility to return a valid value; when an accessor property is written to, a function is called with the new value, and that function must decide how to react to the data. Accessor properties have four attributes:

  • [[Configurable]] — Indicates if the property may be redefined by removing the property via delete, changing the property’s attributes, or changing the property into a data property. By default, this is true for all properties defined directly on an object.
  • [[Enumerable]] — Indicates if the property will be returned in a for-in loop. By default, this is true for all properties defined directly on an object.
  • [[Get]] — The function to call when the property is read from. The default value is undefined.
  • [[Set]] — The function to call when the property is written to. The default value is undefined.

It is not possible to define an accessor property explicitly; you must use Object.defineProperty(). Here’s a simple example:

image
var book = {
    _year: 2004,
    edition: 1
};
  
Object.defineProperty(book, "year", {
    get: function(){
        return this._year;
    },
    set: function(newValue){
    
        if (newValue > 2004) {
            this._year = newValue;
            this.edition += newValue - 2004;
        }
    }
});
 
book.year = 2005;
alert(book.edition);   //2

AccessorPropertiesExample01.htm

In this code, an object book is created with two default properties: _year and edition. The underscore on _year is a common notation to indicate that a property is not intended to be accessed from outside of the object’s methods. The year property is defined to be an accessor property where the getter function simply returns the value of _year and the setter does some calculation to determine the correct edition. So changing the year property to 2005 results in both _year and edition changing to 2. This is a typical use case for accessor properties, when setting a property value results in some other changes to occur.

It’s not necessary to assign both a getter and a setter. Assigning just a getter means that the property cannot be written to and attempts to do so will be ignored. In strict mode, trying to write to a property with only a getter throws an error. Likewise, a property with only a setter cannot be read and will return the value undefined in nonstrict mode, while doing so throws an error in strict mode.

Prior to the ECMAScript 5 method, which is available in Internet Explorer 9+ (Internet Explorer 8 had a partial implementation), Firefox 4+, Safari 5+, Opera 12+, and Chrome, two nonstandard methods were used to create accessor properties: __defineGetter__() and __defineSetter__(). These were first developed by Firefox and later copied by Safari 3, Chrome 1, and Opera 9.5. The previous example can be rewritten using these legacy methods as follows:

image
var book = {
    _year: 2004,
    edition: 1
};
 
//legacy accessor support
book.__defineGetter__("year", function(){
    return this._year;    
});
 
book.__defineSetter__("year", function(newValue){
    if (newValue > 2004) {
        this._year = newValue;
        this.edition += newValue - 2004;
    }    
});
 
book.year = 2005;
alert(book.edition);   //2

AccessorPropertiesExample02.htm

There is no way to modify [[Configurable]] or [[Enumerable]] in browsers that don’t support Object.defineProperty().

Defining Multiple Properties

Since there’s a high likelihood that you’ll need to define more than one property on an object, ECMAScript 5 provides the Object.defineProperties() method. This method allows you to define multiple properties using descriptors at once. There are two arguments: the object on which to add or modify the properties and an object whose property names correspond to the properties’ names to add or modify. For example:

var book = {};
 
Object.defineProperties(book, {
    _year: {
        value: 2004
    },
    
    edition: {
        value: 1
    },
    
    year: {            
        get: function(){
            return this._year;
        },
        
        set: function(newValue){
            if (newValue > 2004) {
                this._year = newValue;
                this.edition += newValue - 2004;
            }                  
        }            
    }        
});

MultiplePropertiesExample01.htm

This code defines two data properties, _year and edition, and an accessor property called year on the book object. The resulting object is identical to the example in the previous section. The only difference is that all of these properties are created at the same time.

The Object.defineProperties() method is supported in Internet Explorer 9+, Firefox 4+, Safari 5+, Opera 12+, and Chrome.

Reading Property Attributes

It’s also possible to retrieve the property descriptor for a given property by using the ECMAScript 5 Object.getOwnPropertyDescriptor() method. This method accepts two arguments: the object on which the property resides and the name of the property whose descriptor should be retrieved. The return value is an object with properties for configurable, enumerable, get, and set for accessor properties or configurable, enumerable, writable, and value for data properties. Example:

image
var book = {};
 
Object.defineProperties(book, {
    _year: {
        value: 2004
    },
    
    edition: {
        value: 1
    },
    
    year: {            
        get: function(){
            return this._year;
        },
        
        set: function(newValue){
            if (newValue > 2004) {
                this._year = newValue;
                this.edition += newValue - 2004;
            }                  
        }            
    }        
});
   
var descriptor = Object.getOwnPropertyDescriptor(book, "_year");
alert(descriptor.value);          //2004
alert(descriptor.configurable);   //false
alert(typeof descriptor.get);     //"undefined"
 
var descriptor = Object.getOwnPropertyDescriptor(book, "year");
alert(descriptor.value);          //undefined
alert(descriptor.enumerable);     //false
alert(typeof descriptor.get);     //"function"

GetPropertyDescriptorExample01.htm

For the data property _year, value is equal to the original value, configurable is false, and get is undefined. For the accessor property year, value is undefined, enumerable is false, and get is a pointer to the specified getter function.

The Object.getOwnPropertyDescriptor() method can be used on any object in JavaScript, including DOM and BOM objects. This method is supported in Internet Explorer 9+, Firefox 4+, Safari 5+, Opera 12+, and Chrome.

OBJECT CREATION

Although using the Object constructor or an object literal are convenient ways to create single objects, there is an obvious downside: creating multiple objects with the same interface requires a lot of code duplication. To solve this problem, developers began using a variation of the factory pattern.

The Factory Pattern

The factory pattern is a well-known design pattern used in software engineering to abstract away the process of creating specific objects. (Other design patterns and their implementation in JavaScript are discussed later in the book.) With no way to define classes in ECMAScript, developers created functions to encapsulate the creation of objects with specific interfaces, such as in this example:

image
function createPerson(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    };    
    return o;
}
                   
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");

FactoryPatternExample01.htm

Here, the function createPerson() accepts arguments with which to build an object with all of the necessary information to represent a Person object. The function can be called any number of times with different arguments and will still return an object that has three properties and one method. Though this solved the problem of creating multiple similar objects, the factory pattern didn’t address the issue of object identification (what type of object an object is). As JavaScript continued to evolve, a new pattern emerged.

The Constructor Pattern

As mentioned in previous chapters, constructors in ECMAScript are used to create specific types of objects. There are native constructors, such as Object and Array, which are available automatically in the execution environment at runtime. It is also possible to define custom constructors that define properties and methods for your own type of object. For instance, the previous example can be rewritten using the constructor pattern as the following:

image
function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function(){
        alert(this.name);
    };    
}
                   
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

ConstructorPatternExample01.htm

In this example, the Person() function takes the place of the factory createPerson() function. Note that the code inside Person() is the same as the code inside createPerson(), with the following exceptions:

  • There is no object being created explicitly.
  • The properties and method are assigned directly onto the this object.
  • There is no return statement.

Also note the name of the function is Person with an uppercase P. By convention, constructor functions always begin with an uppercase letter, whereas nonconstructor functions begin with a lowercase letter. This convention is borrowed from other OO languages and helps to distinguish function use in ECMAScript, since constructors are simply functions that create objects.

To create a new instance of Person, use the new operator. Calling a constructor in this manner essentially causes the following four steps to be taken:

1. Create a new object.

2. Assign the this value of the constructor to the new object (so this points to the new object).

3. Execute the code inside the constructor (adds properties to the new object).

4. Return the new object.

At the end of the preceding example, person1 and person2 are each filled with a different instance of Person. Each of these objects has a constructor property that points back to Person, as follows:

alert(person1.constructor == Person);  //true
alert(person2.constructor == Person);  //true

The constructor property was originally intended for use in identifying the object type. However, the instanceof operator is considered to be a safer way of determining type. Each of the objects in this example is considered to be both an instance of Object and an instance of Person, as indicated by using the instanceof operator like this:

alert(person1 instanceof Object);  //true
alert(person1 instanceof Person);  //true
alert(person2 instanceof Object);  //true
alert(person2 instanceof Person);  //true

Defining your own constructors ensures that instances can be identified as a particular type later on, which is a great advantage over the factory pattern. In this example, person1 and person2 are considered to be instances of Object, because all custom objects inherit from Object (the specifics of this are discussed later).

image

Constructors defined in this manner are defined on the Global object (the window object in web browsers). The Browser Object Model (BOM) is discussed further in Chapter 8.

Constructors as Functions

The only difference between constructor functions and other functions is the way in which they are called. Constructors are, after all, just functions; there’s no special syntax to define a constructor that automatically makes it behave as such. Any function that is called with the new operator acts as a constructor, whereas any function called without it acts just as you would expect a normal function call to act. For instance, the Person() function from the previous example may be called in any of the following ways:

image
//use as a constructor
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName();   //"Nicholas"
                   
//call as a function
Person("Greg", 27, "Doctor");  //adds to window
window.sayName();   //"Greg"
                   
//call in the scope of another object
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName();    //"Kristen"

ConstructorPatternExample02.htm

The first part of this example shows the typical use of a constructor, to create a new object via the new operator. The second part shows what happens when the Person() function is called without the new operator: the properties and methods get added to the window object. Remember that the this object always points to the Global object (window in web browsers) when a function is called without an explicitly set this value (by being an object method or through call()/apply()). So after the function is called, the sayName() method can be called on the window object, and it will return "Greg". The Person() function can also be called within the scope of a particular object using call() (or apply()). In this case, it’s called with a this value of the object o, which then gets assigned all of the properties and the sayName() method.

Problems with Constructors

Though the constructor paradigm is useful, it is not without its faults. The major downside to constructors is that methods are created once for each instance. So, in the previous example, both person1 and person2 have a method called sayName(), but those methods are not the same instance of Function. Remember, functions are objects in ECMAScript, so every time a function is defined, it’s actually an object being instantiated. Logically, the constructor actually looks like this:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = new Function("alert(this.name)"); //logical equivalent 
}

Thinking about the constructor in this manner makes it clear that each instance of Person gets its own instance of Function that happens to display the name property. To be clear, creating a function in this manner is different with regard to scope chains and identifier resolution, but the mechanics of creating a new instance of Function remain the same. So, functions of the same name on different instances are not equivalent, as the following code proves:

alert(person1.sayName == person2.sayName);  //false

It doesn’t make sense to have two instances of Function that do the same thing, especially when the this object makes it possible to avoid binding functions to particular objects until runtime. It’s possible to work around this limitation by moving the function definition outside of the constructor, as follows:

image
function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = sayName;
}
                   
function sayName(){
    alert(this.name);
}
                   
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");

ConstructorPatternExample03.htm

In this example, the sayName() function is defined outside the constructor. Inside the constructor, the sayName property is set equal to the global sayName() function. Since the sayName property now contains just a pointer to a function, both person1 and person2 end up sharing the sayName() function that is defined in the global scope. This solves the problem of having duplicate functions that do the same thing but also creates some clutter in the global scope by introducing a function that can realistically be used only in relation to an object. If the object needed multiple methods, that would mean multiple global functions, and all of a sudden the custom reference type definition is no longer nicely grouped in the code. These problems are addressed by using the prototype pattern.

The Prototype Pattern

Each function is created with a prototype property, which is an object containing properties and methods that should be available to instances of a particular reference type. This object is literally a prototype for the object to be created once the constructor is called. The benefit of using the prototype is that all of its properties and methods are shared among object instances. Instead of assigning object information in the constructor, they can be assigned directly to the prototype, as in this example:

function Person(){
}
                   
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
                   
var person1 = new Person();
person1.sayName();   //"Nicholas"
                   
var person2 = new Person();
person2.sayName();   //"Nicholas"
                   
alert(person1.sayName == person2.sayName);  //true

PrototypePatternExample01.htm

Here, the properties and the sayName() method are added directly to the prototype property of Person, leaving the constructor empty. However, it’s still possible to call the constructor to create a new object and have the properties and methods present. Unlike the constructor pattern, the properties and methods are all shared among instances, so person1 and person2 are both accessing the same set of properties and the same sayName() function. To understand how this works, you must understand the nature of prototypes in ECMAScript.

How Prototypes Work

Whenever a function is created, its prototype property is also created according to a specific set of rules. By default, all prototypes automatically get a property called constructor that points back to the function on which it is a property. In the previous example, for instance, Person.prototype.constructor points to Person. Then, depending on the constructor, other properties and methods may be added to the prototype.

When defining a custom constructor, the prototype gets the constructor property only by default; all other methods are inherited from Object. Each time the constructor is called to create a new instance, that instance has an internal pointer to the constructor’s prototype. In ECMA-262 fifth edition, this is called [[Prototype]]. There is no standard way to access [[Prototype]] from script, but Firefox, Safari, and Chrome all support a property on every object called __proto__.; in other implementations, this property is completely hidden from script. The important thing to understand is that a direct link exists between the instance and the constructor’s prototype but not between the instance and the constructor.

Consider the previous example using the Person constructor and Person.prototype. The relationship between the objects in the example is shown in Figure 6-1.

Figure 6-1 shows the relationship between the Person constructor, the Person’s prototype, and the two instances of Person that exist. Note that Person.prototype points to the prototype object but Person.prototype.constructor points back to Person. The prototype contains the constructor property and the other properties that were added. Each instance of Person, person1, and person2 has internal properties that point back to Person.prototype only; each has no direct relationship with the constructor. Also note that even though neither of these instances have properties or methods, person1.sayName() works. This is due to the lookup procedure for object properties.

Even though [[Prototype]] is not accessible in all implementations, the isPrototypeOf() method can be used to determine if this relationship exists between objects. Essentially, isPrototypeOf() returns true if [[Prototype]] points to the prototype on which the method is being called, as shown here:

alert(Person.prototype.isPrototypeOf(person1));  //true
alert(Person.prototype.isPrototypeOf(person2));  //true

In this code, the prototype’s isPrototypeOf() method is called on both person1 and person2. Since both instances have a link to Person.prototype, it returns true.

ECMAScript 5 adds a new method called Object.getPrototypeOf(), which returns the value of [[Prototype]] in all supporting implementations. For example:

alert(Object.getPrototypeOf(person1) == Person.prototype);  //true
alert(Object.getPrototypeOf(person1).name);  //"Nicholas"

The first line of this code simply confirms that the object returned from Object.getPrototypeOf() is actually the prototype of the object. The second line retrieves the value of the name property on the prototype, which is "Nicholas". Using Object.getPrototypeOf(), you are able to retrieve an object’s prototype easily, which becomes important once you want to implement inheritance using the prototype (discussed later in this chapter). This method is supported in Internet Explorer 9+, Firefox 3.5+, Safari 5+, Opera 12+, and Chrome.

Whenever a property is accessed for reading on an object, a search is started to find a property with that name. The search begins on the object instance itself. If a property with the given name is found on the instance, then that value is returned; if the property is not found, then the search continues up the pointer to the prototype, and the prototype is searched for a property with the same name. If the property is found on the prototype, then that value is returned. So, when person1.sayName() is called, a two-step process happens. First, the JavaScript engine asks, “Does the instance person1 have a property called sayName?” The answer is no, so it continues the search and asks, “Does the person1 prototype have a property called sayName?” The answer is yes, so the function stored on the prototype is accessed. When person2.sayName() is called, the same search executes, ending with the same result. This is how prototypes are used to share properties and methods among multiple object instances.

image

The constructor property mentioned earlier exists only on the prototype and so is accessible from object instances.

Although it’s possible to read values on the prototype from object instances, it is not possible to overwrite them. If you add a property to an instance that has the same name as a property on the prototype, you create the property on the instance, which then masks the property on the prototype. Here’s an example:

image
function Person(){
}
                   
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
                   
var person1 = new Person();
var person2 = new Person();
                   
person1.name = "Greg";
alert(person1.name);     //"Greg" - from instance
alert(person2.name);     //"Nicholas" - from prototype

PrototypePatternExample02.htm

In this example, the name property of person1 is shadowed by a new value. Both person1.name and person2.name still function appropriately, returning "Greg" (from the object instance) and "Nicholas" (from the prototype), respectively. When person1.name was accessed in the alert(), its value was read, so the search began for a property called name on the instance. Since the property exists, it is used without searching the prototype. When person2.name is accessed the same way, the search doesn’t find the property on the instance, so it continues to search on the prototype where the name property is found.

Once a property is added to the object instance, it shadows any properties of the same name on the prototype, which means that it blocks access to the property on the prototype without altering it. Even setting the property to null only sets the property on the instance and doesn’t restore the link to the prototype. The delete operator, however, completely removes the instance property and allows the prototype property to be accessed again as follows:

function Person(){
}
                   
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
                   
var person1 = new Person();
var person2 = new Person();
                   
person1.name = "Greg";
alert(person1.name);   //"Greg" - from instance
alert(person2.name);   //"Nicholas" - from prototype
                   
delete person1.name;
alert(person1.name);   //"Nicholas" - from the prototype

PrototypePatternExample03.htm

In this modified example, delete is called on person1.name, which previously had been shadowed with the value "Greg". This restores the link to the prototype’s name property, so the next time person1.name is accessed, it’s the prototype property’s value that is returned.

The hasOwnProperty() method determines if a property exists on the instance or on the prototype. This method, which is inherited from Object, returns true only if a property of the given name exists on the object instance, as in this example:

function Person(){
}
                   
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
                   
var person1 = new Person();
var person2 = new Person();
                   
alert(person1.hasOwnProperty("name"));  //false
                   
person1.name = "Greg";
alert(person1.name);   //"Greg" - from instance
alert(person1.hasOwnProperty("name"));  //true
                   
 
alert(person2.name);   //"Nicholas" - from prototype
alert(person2.hasOwnProperty("name"));  //false
                   
delete person1.name;
alert(person1.name);   //"Nicholas" - from the prototype
alert(person1.hasOwnProperty("name"));  //false

By injecting calls to hasOwnProperty() in this example, it becomes clear when the instance’s property is being accessed and when the prototype’s property is being accessed. Calling person1.hasOwnProperty("name") returns true only after name has been overwritten on person1, indicating that it now has an instance property instead of a prototype property. Figure 6-2 illustrates the various steps being taken in this example. (For simplicity, the relationship to the Person constructor has been omitted.)

image

The ECMAScript 5 Object.getOwnPropertyDescriptor() method works only on instance properties; to retrieve the descriptor of a prototype property, you must call Object.getOwnPropertyDescriptor() on the prototype object directly.

Prototypes and the in Operator

There are two ways to use the in operator: on its own or as a for-in loop. When used on its own, the in operator returns true when a property of the given name is accessible by the object, which is to say that the property may exist on the instance or on the prototype. Consider the following example:

image
function Person(){
}
                   
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
                   
var person1 = new Person();
var person2 = new Person();
                   
alert(person1.hasOwnProperty("name"));  //false
alert("name" in person1);  //true
                   
person1.name = "Greg";
alert(person1.name);   //"Greg" - from instance
alert(person1.hasOwnProperty("name"));  //true
alert("name" in person1);  //true
                   
alert(person2.name);   //"Nicholas" - from prototype
alert(person2.hasOwnProperty("name"));  //false
alert("name" in person2);  //true
                   
delete person1.name;
alert(person1.name);   //"Nicholas" - from the prototype
alert(person1.hasOwnProperty("name"));  //false
alert("name" in person1);  //true

PrototypePatternExample04.htm

Throughout the execution of this code, the property name is available on each object either directly or from the prototype. Therefore, calling "name" in person1 always returns true, regardless of whether the property exists on the instance. It’s possible to determine if the property of an object exists on the prototype by combining a call to hasOwnProperty() with the in operator like this:

image
function hasPrototypeProperty(object, name){
    return !object.hasOwnProperty(name) && (name in object);
}

Since the in operator always returns true so long as the property is accessible by the object, and hasOwnProperty() returns true only if the property exists on the instance, a prototype property can be determined if the in operator returns true but hasOwnProperty() returns false. Consider the following example:

function Person(){
}
                   
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
                   
var person = new Person();        
alert(hasPrototypeProperty(person, "name"));  //true
                   
person.name = "Greg";
alert(hasPrototypeProperty(person, "name"));  //false       

PrototypePatternExample05.htm

In this code, the name property first exists on the prototype, so hasPrototypeProperty() returns true. Once the name property is overwritten, it exists on the instance, so hasPrototypeProperty() returns false. Even though the name property still exists on the prototype, it is no longer used because the instance property now exists.

When using a for-in loop, all properties that are accessible by the object and can be enumerated will be returned, which includes properties both on the instance and on the prototype. Instance properties that shadow a non-enumerable prototype property (a property that has [[Enumerable]] set to false) will be returned in the for-in loop, since all developer-defined properties are enumerable by rule, except in Internet Explorer 8 and earlier.

The old Internet Explorer implementation has a bug where properties that shadow non-enumerable properties will not show up in a for-in loop. Here’s an example:

image
var o = {
    toString : function(){
        return "My Object";
    }
};
                   
for (var prop in o){
    if (prop == "toString"){
        alert("Found toString");   //won't display in Internet Explorer
    }
}

PrototypePatternExample06.htm

When this code is run, a single alert should be displayed indicating that the toString() method was found. The object o has an instance property called toString() that shadows the prototype’s toString() method (which is not enumerable). In Internet Explorer, this alert is never displayed because it skips over the property, honoring the [[Enumerable]] attribute that was set on the prototype’s toString() method. This same bug affects all properties and methods that aren’t enumerable by default: hasOwnProperty(), propertyIsEnumerable(), toLocaleString(), toString(), and valueOf(). ECMAScript 5 sets [[Enumerable]] to false on the constructor and prototype properties, but this is inconsistent across implementations.

To retrieve a list of all enumerable instance properties on an object, you can use the ECMAScript 5 Object.keys() method, which accepts an object as its argument and returns an array of strings containing the names of all enumerable properties. For example:

image
function Person(){
}
 
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
    alert(this.name);
};
 
var keys = Object.keys(Person.prototype);
alert(keys);       //"name,age,job,sayName"
 
var p1 = new Person();
p1.name = "Rob";
p1.age = 31;
var p1keys = Object.keys(p1);
alert(p1keys);    //"name,age"

ObjectKeysExample01.htm

Here, the keys variable is filled with an array containing "name", "age", "job", and "sayName". This is the order in which they would normally appear using for-in. When called on an instance of Person, Object.keys() returns an array of name and age, the only two instance properties.

If you’d like a list of all instance properties, whether enumerable or not, you can use Object.getOwnPropertyNames() in the same way:

var keys = Object.getOwnPropertyNames(Person.prototype);
alert(keys);   //"constructor,name,age,job,sayName"

ObjectPropertyNamesExample01.htm

Note the inclusion of the non-enumerable constructor property in the list of results. Both Object.keys() and Object.getOwnPropertyNames() may be suitable replacements for using for-in. These methods are supported in Internet Explorer 9+, Firefox 4+, Safari 5+, Opera 12+, and Chrome.

Alternate Prototype Syntax

You may have noticed in the previous example that Person.prototype had to be typed out for each property and method. To limit this redundancy and to better visually encapsulate functionality on the prototype, it has become more common to simply overwrite the prototype with an object literal that contains all of the properties and methods, as in this example:

image
function Person(){
}
                   
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

PrototypePatternExample07.htm

In this rewritten example, the Person.prototype property is set equal to a new object created with an object literal. The end result is the same, with one exception: the constructor property no longer points to Person. When a function is created, its prototype object is created and the constructor is automatically assigned. Essentially, this syntax overwrites the default prototype object completely, meaning that the constructor property is equal to that of a completely new object (the Object constructor) instead of the function itself. Although the instanceof operator still works reliably, you cannot rely on the constructor to indicate the type of object, as this example shows:

var friend = new Person();
alert(friend instanceof Object);      //true
alert(friend instanceof Person);      //true
alert(friend.constructor == Person);  //false
alert(friend.constructor == Object);  //true

PrototypePatternExample07.htm

Here, instanceof still returns true for both Object and Person, but the constructor property is now equal to Object instead of Person. If the constructor’s value is important, you can set it specifically back to the appropriate value, as shown here:

function Person(){
}
                   
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};

PrototypePatternExample08.htm

This code specifically includes a constructor property and sets it equal to Person, ensuring that the property contains the appropriate value.

Keep in mind that restoring the constructor in this manner creates a property with [[Enumerable]] set to true. Native constructor properties are not enumerable by default, so if you’re using an ECMAScript 5–compliant JavaScript engine, you may wish to use Object.defineProperty() instead:

function Person(){
}
                   
Person.prototype = {
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
 
//ECMAScript 5 only - restore the constructor
Object.defineProperty(Person.prototype, "constructor", {
    enumerable: false,
    value: Person
});

Dynamic Nature of Prototypes

Since the process of looking up values on a prototype is a search, changes made to the prototype at any point are immediately reflected on instances, even the instances that existed before the change was made. Here’s an example:

image
var friend= new Person();
                   
Person.prototype.sayHi = function(){
    alert("hi");
};
                   
friend.sayHi();   //"hi" - works!

PrototypePatternExample09.htm

In this code, an instance of Person is created and stored in friend. The next statement adds a method called sayHi() to Person.prototype. Even though the friend instance was created prior to this change, it still has access to the new method. This happens because of the loose link between the instance and the prototype. When friend.sayHi() is called, the instance is first searched for a property named sayHi; when it’s not found, the search continues to the prototype. Since the link between the instance and the prototype is simply a pointer, not a copy, the search finds the new sayHi property on the prototype and returns the function stored there.

Although properties and methods may be added to the prototype at any time, and they are reflected instantly by all object instances, you cannot overwrite the entire prototype and expect the same behavior. The [[Prototype]] pointer is assigned when the constructor is called, so changing the prototype to a different object severs the tie between the constructor and the original prototype. Remember: the instance has a pointer to only the prototype, not to the constructor. Consider the following:

image
function Person(){
}
                   
var friend = new Person();
        
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    sayName : function () {
        alert(this.name);
    }
};
                   
friend.sayName();   //error

PrototypePatternExample10.htm

In this example, a new instance of Person is created before the prototype object is overwritten. When friend.sayName() is called, it causes an error, because the prototype that friend points to doesn’t contain a property of that name. Figure 6-3 illustrates why this happens.

Overwriting the prototype on the constructor means that new instances will reference the new prototype while any previously existing object instances still reference the old prototype.

Native Object Prototypes

The prototype pattern is important not just for defining custom types but also because it is the pattern used to implement all of the native reference types. Each of these (including Object, Array, String, and so on) has its methods defined on the constructor’s prototype. For instance, the sort() method can be found on Array.prototype, and substring() can be found on String.prototype, as shown here:

image
alert(typeof Array.prototype.sort);         //"function"
alert(typeof String.prototype.substring);   //"function"

Through native object prototypes, it’s possible to get references to all of the default methods and to define new methods. Native object prototypes can be modified just like custom object prototypes, so methods can be added at any time. For example, the following code adds a method called startsWith() to the String primitive wrapper:

String.prototype.startsWith = function (text) {
    return this.indexOf(text) == 0;
};
                   
var msg = "Hello world!";
alert(msg.startsWith("Hello"));   //true

PrototypePatternExample11.htm

The startsWith() method in this example returns true if some given text occurs at the beginning of a string. The method is assigned to String.prototype, making it available to all strings in the environment. Since msg is a string, the String primitive wrapper is created behind the scenes, making startsWith() accessible.

image

Although possible, it is not recommended to modify native object prototypes in a production environment. This can often cause confusion and create possible name collisions if a method that didn’t exist natively in one browser is implemented natively in another. It’s also possible to accidentally overwrite native methods.

Problems with Prototypes

The prototype pattern isn’t without its faults. For one, it negates the ability to pass initialization arguments into the constructor, meaning that all instances get the same property values by default. Although this is an inconvenience, it isn’t the biggest problem with prototypes. The main problem comes with their shared nature.

All properties on the prototype are shared among instances, which is ideal for functions. Properties that contain primitive values also tend to work well, as shown in the previous example, where it’s possible to hide the prototype property by assigning a property of the same name to the instance. The real problem occurs when a property contains a reference value. Consider the following example:

image
function Person(){
}
                   
Person.prototype = {
    constructor: Person,
    name : "Nicholas",
    age : 29,
    job : "Software Engineer",
    friends : ["Shelby", "Court"],
    sayName : function () {
        alert(this.name);
    }
};
                   
var person1 = new Person();
var person2 = new Person();
                   
person1.friends.push("Van");
                   
alert(person1.friends);    //"Shelby,Court,Van"
alert(person2.friends);    //"Shelby,Court,Van"
alert(person1.friends === person2.friends);  //true

PrototypePatternExample12.htm

Here, the Person.prototype object has a property called friends that contains an array of strings. Two instances of Person are then created. The person1.friends array is altered by adding another string. Because the friends array exists on Person.prototype, not on person1, the changes made are also reflected on person2.friends (which points to the same array). If the intention is to have an array shared by all instances, then this outcome is okay. Typically, though, instances want to have their own copies of all properties. This is why the prototype pattern is rarely used on its own.

Combination Constructor/Prototype Pattern

The most common way of defining custom types is to combine the constructor and prototype patterns. The constructor pattern defines instance properties, whereas the prototype pattern defines methods and shared properties. With this approach, each instance ends up with its own copy of the instance properties, but they all share references to methods, conserving memory. This pattern allows arguments to be passed into the constructor as well, effectively combining the best parts of each pattern. The previous example can now be rewritten as follows:

function Person(name, age, job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby", "Court"];
}
                   
Person.prototype = {
    constructor: Person,
    sayName : function () {
        alert(this.name);
    }
};
                   
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
                   
person1.friends.push("Van");
                   
alert(person1.friends);    //"Shelby,Court,Van"
alert(person2.friends);    //"Shelby,Court"
alert(person1.friends === person2.friends);  //false
alert(person1.sayName === person2.sayName);  //true

HybridPatternExample01.htm

Note that the instance properties are now defined solely in the constructor, and the shared property constructor and the method sayName() are defined on the prototype. When person1.friends is augmented by adding a new string, person2.friends is not affected, because they each have separate arrays.

The hybrid constructor/prototype pattern is the most widely used and accepted practice for defining custom reference types in ECMAScript. Generally speaking, this is the default pattern to use for defining reference types.

Dynamic Prototype Pattern

Developers coming from other OO languages may find the visual separation between the constructor and the prototype confusing. The dynamic prototype pattern seeks to solve this problem by encapsulating all of the information within the constructor while maintaining the benefits of using both a constructor and a prototype by initializing the prototype inside the constructor, but only if it is needed. You can determine if the prototype needs to be initialized by checking for the existence of a method that should be available. Consider this example:

image
function Person(name, age, job){
                   
    //properties
    this.name = name;
    this.age = age;
    this.job = job;
    
    //methods
    if (typeof this.sayName != "function"){
    
        Person.prototype.sayName = function(){
            alert(this.name);
        };
        
    }
}
                   
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();

DynamicPrototypeExample01.htm

The highlighted section of code inside the constructor adds the sayName() method if it doesn’t already exist. This block of code is executed only the first time the constructor is called. After that, the prototype has been initialized and doesn’t need any further modification. Remember that changes to the prototype are reflected immediately in all instances, so this approach works perfectly. The if statement may check for any property or method that will be present once initialized — there’s no need for multiple if statements to check each property or method; any one will do. This pattern preserves the use of instanceof in determining what type of object was created.

image

You cannot overwrite the prototype using an object literal when using the dynamic prototype pattern. As described previously, overwriting a prototype when an instance already exists effectively cuts off that instance from the new prototype.

Parasitic Constructor Pattern

The parasitic constructor pattern is typically a fallback when the other patterns fail. The basic idea of this pattern is to create a constructor that simply wraps the creation and return of another object while looking like a typical constructor. Here’s an example:

image
function Person(name, age, job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function(){
        alert(this.name);
    };    
    return o;
}
                   
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();  //"Nicholas"

HybridFactoryPatternExample01.htm

In this example, the Person constructor creates a new object, initializes it with properties and methods, and then returns the object. This is exactly the same as the factory pattern except that the function is called as a constructor, using the new operator. When a constructor doesn’t return a value, it returns the new object instance by default. Adding a return statement at the end of a constructor allows you to override the value that is returned when the constructor is called.

This pattern allows you to create constructors for objects that may not be possible otherwise. For example, you may want to create a special array that has an extra method. Since you don’t have direct access to the Array constructor, this pattern works:

image
function SpecialArray(){       
                   
    //create the array
    var values = new Array();
    
    //add the values
    values.push.apply(values, arguments);
    
    //assign the method
    values.toPipedString = function(){
        return this.join("|");
    };
    
    //return it
    return values;        
}
                   
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //"red|blue|green"

HybridFactoryPatternExample02.htm

In this example, a constructor called SpecialArray is created. In the constructor, a new array is created and initialized using the push() method (which has all of the constructor arguments passed in). Then a method called toPipedString() is added to the instance, which simply outputs the array values as a pipe-delimited list. The last step is to return the array as the function value. Once that is complete, the SpecialArray constructor can be called, passing in the initial values for the array, and toPipedString() can be called.

A few important things to note about this pattern: there is no relationship between the returned object and the constructor or the constructor’s prototype; the object exists just as if it were created outside of a constructor. Therefore, you cannot rely on the instanceof operator to indicate the object type. Because of these issues, this pattern should not be used when other patterns work.

Durable Constructor Pattern

Douglas Crockford coined the term durable objects in JavaScript to refer to objects that have no public properties and whose methods don’t reference the this object. Durable objects are best used in secure environments (those that forbid the use of this and new) or to protect data from the rest of the application (as in mashups). A durable constructor is a constructor that follows a pattern similar to the parasitic constructor pattern, with two differences: instance methods on the created object don’t refer to this, and the constructor is never called using the new operator. The Person constructor from the previous section can be rewritten as a durable constructor like this:

function Person(name, age, job){
                   
    //create the object to return
    var o = new Object();
                   
    //optional: define private variables/functions here
                   
    //attach methods
    o.sayName = function(){
        alert(name);
    };    
                   
    //return the object
    return o;
}

Note that there is no way to access the value of name from the returned object. The sayName() method has access to it, but nothing else does. The Person durable constructor is used as follows:

var friend = Person("Nicholas", 29, "Software Engineer");
friend.sayName();  //"Nicholas"

The person variable is a durable object, and there is no way to access any of its data members without calling a method. Even if some other code adds methods or data members to the object, there is no way to access the original data that was passed into the constructor. Such security makes the durable constructor pattern useful when dealing with secure execution environments such as those provided by ADsafe (www.adsafe.org) or Caja (http://code.google.com/p/google-caja/).

image

As with the parasitic constructor pattern, there is no relationship between the constructor and the object instance, so instanceof will not work.

INHERITANCE

The concept most often discussed in relation to OO programming is inheritance. Many OO languages support two types of inheritance: interface inheritance, where only the method signatures are inherited, and implementation inheritance, where actual methods are inherited. Interface inheritance is not possible in ECMAScript, because, as mentioned previously, functions do not have signatures. Implementation inheritance is the only type of inheritance supported by ECMAScript, and this is done primarily through the use of prototype chaining.

Prototype Chaining

ECMA-262 describes prototype chaining as the primary method of inheritance in ECMAScript. The basic idea is to use the concept of prototypes to inherit properties and methods between two reference types. Recall the relationship between constructors, prototypes, and instances: each constructor has a prototype object that points back to the constructor, and instances have an internal pointer to the prototype. What if the prototype were actually an instance of another type? That would mean the prototype itself would have a pointer to a different prototype that, in turn, would have a pointer to another constructor. If that prototype were also an instance of another type, then the pattern would continue, forming a chain between instances and prototypes. This is the basic idea behind prototype chaining.

Implementing prototype chaining involves the following code pattern:

image
function SuperType(){
    this.property = true;
}
                   
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
                   
function SubType(){
    this.subproperty = false;
}
                   
//inherit from SuperType
SubType.prototype = new SuperType();
                   
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
                   
var instance = new SubType();
alert(instance.getSuperValue());   //true

PrototypeChainingExample01.htm

This code defines two types: SuperType and SubType. Each type has a single property and a single method. The main difference between the two is that SubType inherits from SuperType by creating a new instance of SuperType and assigning it to SubType.prototype. This overwrites the original prototype and replaces it with a new object, which means that all properties and methods that typically exist on an instance of SuperType now also exist on SubType.prototype. After the inheritance takes place, a method is assigned to SubType.prototype, adding a new method on top of what was inherited from SuperType. The relationship between the instance and both constructors and prototypes is displayed in Figure 6-4.

Instead of using the default prototype of SubType, a new prototype is assigned. That new prototype happens to be an instance of SuperType, so it not only gets the properties and methods of a SuperType instance but also points back to the SuperType’s prototype. So instance points to SubType.prototype, and SubType.prototype points to SuperType.prototype. Note that the getSuperValue() method remains on the SuperType.prototype object, but property ends up on SubType.prototype. That’s because getSuperValue() is a prototype method, and property is an instance property. SubType.prototype is now an instance of SuperType, so property is stored there. Also note that instance.constructor points to SuperType, because the constructor property on the SubType.prototype was overwritten.

Prototype chaining extends to the prototype search mechanism described earlier. As you may recall, when a property is accessed in read mode on an instance, the property is first searched for on the instance. If the property is not found, then the search continues to the prototype. When inheritance has been implemented via prototype chaining, that search can continue up the prototype chain. In the previous example, for instance, a call to instance.getSuperValue() results in a three-step search: 1) the instance, 2) SubType.prototype, and 3) SuperType.prototype, where the method is found. The search for properties and methods always continues until the end of the prototype chain is reached.

Default Prototypes

In reality, there is another step in the prototype chain. All reference types inherit from Object by default, which is accomplished through prototype chaining. The default prototype for any function is an instance of Object, meaning that its internal prototype pointer points to Object.prototype. This is how custom types inherit all of the default methods such as toString() and valueOf(). So the previous example has an extra layer of inheritance. Figure 6-5 shows the complete prototype chain.

SubType inherits from SuperType, and SuperType inherits from Object. When instance.toString() is called, the method being called actually exists on Object.prototype.

Prototype and Instance Relationships

The relationship between prototypes and instances is discernible in two ways. The first way is to use the instanceof operator, which returns true whenever an instance is used with a constructor that appears in its prototype chain, as in this example:

image
alert(instance instanceof Object);      //true
alert(instance instanceof SuperType);   //true
alert(instance instanceof SubType);     //true

PrototypeChainingExample01.htm

Here, the instance object is technically an instance of Object, SuperType, and SubType because of the prototype chain relationship. The result is that instanceof returns true for all of these constructors.

The second way to determine this relationship is to use the isPrototypeOf() method. Each prototype in the chain has access to this method, which returns true for an instance in the chain, as in this example:

alert(Object.prototype.isPrototypeOf(instance));    //true
alert(SuperType.prototype.isPrototypeOf(instance)); //true
alert(SubType.prototype.isPrototypeOf(instance));   //true

PrototypeChainingExample01.htm

Working with Methods

Often a subtype will need to either override a supertype method or introduce new methods that don’t exist on the supertype. To accomplish this, the methods must be added to the prototype after the prototype has been assigned. Consider this example:

image
function SuperType(){
    this.property = true;
}
                   
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
                   
function SubType(){
    this.subproperty = false;
}
                   
//inherit from SuperType
SubType.prototype = new SuperType();
                   
//new method
SubType.prototype.getSubValue = function (){
    return this.subproperty;
};
                   
//override existing method
SubType.prototype.getSuperValue = function (){
    return false;
};
                   
var instance = new SubType();
alert(instance.getSuperValue());   //false

PrototypeChainingExample02.htm

In this code, the highlighted area shows two methods. The first is getSubValue(), which is a new method on the SubType. The second is getSuperValue(), which already exists in the prototype chain but is being shadowed here. When getSuperValue() is called on an instance of SubType, it will call this one, but instances of SuperType will still call the original. The important thing to note is that both of the methods are defined after the prototype has been assigned as an instance of SuperType.

Another important thing to understand is that the object literal approach to creating prototype methods cannot be used with prototype chaining, because you end up overwriting the chain. Here’s an example:

image
function SuperType(){
    this.property = true;
}
                   
SuperType.prototype.getSuperValue = function(){
    return this.property;
};
                   
function SubType(){
    this.subproperty = false;
}
                   
//inherit from SuperType
SubType.prototype = new SuperType();
                   
//try to add new methods - this nullifies the previous line
SubType.prototype = {
    getSubValue : function (){
        return this.subproperty;
    },
                   
    someOtherMethod : function (){
        return false;
    }
};
                   
var instance = new SubType();
alert(instance.getSuperValue());   //error!

PrototypeChainingExample03.htm

In this code, the prototype is reassigned to be an object literal after it was already assigned to be an instance of SuperType. The prototype now contains a new instance of Object instead of an instance of SuperType, so the prototype chain has been broken — there is no relationship between SubType and SuperType.

Problems with Prototype Chaining

Even though prototype chaining is a powerful tool for inheritance, it is not without its issues. The major issue revolves around prototypes that contain reference values. Recall from earlier that prototype properties containing reference values are shared with all instances; this is why properties are typically defined within the constructor instead of on the prototype. When implementing inheritance using prototypes, the prototype actually becomes an instance of another type, meaning that what once were instance properties are now prototype properties. The issue is highlighted by the following example:

image
function SuperType(){
    this.colors = ["red", "blue", "green"];
}
                   
function SubType(){            
}
                   
//inherit from SuperType
SubType.prototype = new SuperType();
                   
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);    //"red,blue,green,black"
                   
var instance2 = new SubType();
alert(instance2.colors);    //"red,blue,green,black"

PrototypeChainingExample04.htm

In this example, the SuperType constructor defines a property colors that contains an array (a reference value). Each instance of SuperType has its own colors property containing its own array. When SubType inherits from SuperType via prototype chaining, SubType.prototype becomes an instance of SuperType and so it gets its own colors property, which is akin to specifically creating SubType.prototype.colors. The end result: all instances of SubType share a colors property. This is indicated as the changes made to instance1.colors are reflected on instance2.colors.

A second issue with prototype chaining is that you cannot pass arguments into the supertype constructor when the subtype instance is being created. In fact, there is no way to pass arguments into the supertype constructor without affecting all of the object instances. Because of this and the aforementioned issue with reference values on the prototype, prototype chaining is rarely used alone.

Constructor Stealing

In an attempt to solve the inheritance problem with reference values on prototypes, developers began using a technique called constructor stealing (also sometimes called object masquerading or classical inheritance). The basic idea is quite simple: call the supertype constructor from within the subtype constructor. Keeping in mind that functions are simply objects that execute code in a particular context, the apply() and call() methods can be used to execute a constructor on the newly created object, as in this example:

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
                   
function SubType(){  
    //inherit from SuperType
    SuperType.call(this);
}
                   
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors);    //"red,blue,green,black"
                   
var instance2 = new SubType();
alert(instance2.colors);    //"red,blue,green"

ConstructorStealingExample01.htm

The highlighted lines in this example show the single call that is used in constructor stealing. By using the call() method (or alternately, apply()), the SuperType constructor is called in the context of the newly created instance of SubType. Doing this effectively runs all of the object-initialization code in the SuperType() function on the new SubType object. The result is that each instance has its own copy of the colors property.

Passing Arguments

One advantage that constructor stealing offers over prototype chaining is the ability to pass arguments into the supertype constructor from within the subtype constructor. Consider the following:

image
function SuperType(name){
    this.name = name;
}
                   
function SubType(){  
    //inherit from SuperType passing in an argument
    SuperType.call(this, "Nicholas");
    
    //instance property
    this.age = 29;
}
                   
var instance = new SubType();
alert(instance.name);    //"Nicholas";
alert(instance.age);     //29

ConstructorStealingExample02.htm

In this code, the SuperType constructor accepts a single argument, name, which is simply assigned to a property. A value can be passed into the SuperType constructor when called from within the SubType constructor, effectively setting the name property for the SubType instance. To ensure that the SuperType constructor doesn’t overwrite those properties, you can define additional properties on the subtype after the call to the supertype constructor.

Problems with Constructor Stealing

The downside to using constructor stealing exclusively is that it introduces the same problems as the constructor pattern for custom types: methods must be defined inside the constructor, so there’s no function reuse. Furthermore, methods defined on the supertype’s prototype are not accessible on the subtype, so all types can use only the constructor pattern. Because of these issues, constructor stealing is rarely used on its own.

Combination Inheritance

Combination inheritance (sometimes also called pseudoclassical inheritance) combines prototype chaining and constructor stealing to get the best of each approach. The basic idea is to use prototype chaining to inherit properties and methods on the prototype and to use constructor stealing to inherit instance properties. This allows function reuse by defining methods on the prototype and allows each instance to have its own properties. Consider the following:

image
function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
                   
SuperType.prototype.sayName = function(){
    alert(this.name);
};
                   
 
function SubType(name, age){  
                   
    //inherit properties
    SuperType.call(this, name);
    
    this.age = age;
}
                   
//inherit methods
SubType.prototype = new SuperType();
                   
SubType.prototype.sayAge = function(){
    alert(this.age);
};
                   
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors);  //"red,blue,green,black"
instance1.sayName();      //"Nicholas";
instance1.sayAge();       //29
                   
var instance2 = new SubType("Greg", 27);
alert(instance2.colors);  //"red,blue,green"
instance2.sayName();      //"Greg";
instance2.sayAge();       //27

CombinationInheritanceExample01.htm

In this example, the SuperType constructor defines two properties, name and colors, and the SuperType prototype has a single method called sayName(). The SubType constructor calls the SuperType constructor, passing in the name argument, and defines its own property called age. Additionally, the SubType prototype is assigned to be an instance of SuperType, and then a new method called sayAge() is defined. With this code, it’s then possible to create two separate instances of SubType that have their own properties, including the colors property, but all use the same methods.

Addressing the downsides of both prototype chaining and constructor stealing, combination inheritance is the most frequently used inheritance pattern in JavaScript. It also preserves the behavior of instanceof and isPrototypeOf() for identifying the composition of objects.

Prototypal Inheritance

In 2006, Douglas Crockford wrote an article titled “Prototypal Inheritance in JavaScript” in which he introduced a method of inheritance that didn’t involve the use of strictly defined constructors. His premise was that prototypes allow you to create new objects based on existing objects without the need for defining custom types. The function he introduced to this end is as follows:

image
function object(o){
    function F(){}
    F.prototype = o;
    return new F();
}

The object() function creates a temporary constructor, assigns a given object as the constructor’s prototype, and returns a new instance of the temporary type. Essentially, object() performs a shadow copy of any object that is passed into it. Consider the following:

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
                   
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
                   
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
                   
alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

PrototypalInheritanceExample01.htm

This is the way Crockford advocates using prototypal inheritance: you have an object that you want to use as the base of another object. That object should be passed into object(), and the resulting object should be modified accordingly. In this example, the person object contains information that should be available on another object, so it is passed into the object() function, which returns a new object. The new object has person as its prototype, meaning that it has both a primitive value property and a reference value property on its prototype. This also means that person.friends is shared not only by person but also with anotherPerson and yetAnotherPerson. Effectively, this code has created two clones of person.

ECMAScript 5 formalized the concept of prototypal inheritance by adding the Object.create() method. This method accepts two arguments, an object to use as the prototype for a new object and an optional object defining additional properties to apply to the new object. When used with one argument, Object.create() behaves the same as the object() method:

image
var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
                   
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
                   
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
                   
alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"

PrototypalInheritanceExample02.htm

The second argument for Object.create() is in the same format as the second argument for Object.defineProperties(): each additional property to define is specified along with its descriptor. Any properties specified in this manner will shadow properties of the same name on the prototype object. For example:

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
                   
var anotherPerson = Object.create(person, {
    name: {
        value: "Greg"
    }
});
 
alert(anotherPerson.name);  //"Greg"               

PrototypalInheritanceExample03.htm

The Object.create() method is supported in Internet Explorer 9+, Firefox 4+, Safari 5+, Opera 12+, and Chrome.

Prototypal inheritance is useful when there is no need for the overhead of creating separate constructors, but you still need an object to behave similarly to another. Keep in mind that properties containing reference values will always share those values, similar to using the prototype pattern.

Parasitic Inheritance

Closely related to prototypal inheritance is the concept of parasitic inheritance, another pattern popularized by Crockford. The idea behind parasitic inheritance is similar to that of the parasitic constructor and factory patterns: create a function that does the inheritance, augments the object in some way, and then returns the object as if it did all the work. The basic parasitic inheritance pattern looks like this:

function createAnother(original){
    var clone = object(original);     //create a new object by calling a function
    clone.sayHi = function(){         //augment the object in some way
        alert("hi");
    };
    return clone;                     //return the object
}

In this code, the createAnother() function accepts a single argument, which is the object to base a new object on. This object, original, is passed into the object() function, and the result is assigned to clone. Next, the clone object is changed to have a new method called sayHi(). The last step is to return the object. The createAnother() function can be used in the following way:

var person = {
    name: "Nicholas",
    friends: ["Shelby", "Court", "Van"]
};
                   
var anotherPerson = createAnother(person);
anotherPerson.sayHi();  //"hi"

The code in this example returns a new object based on person. The anotherPerson object has all of the properties and methods of person but adds a new method called sayHi().

Parasitic inheritance is another pattern to use when you are concerned primarily with objects and not with custom types and constructors. The object() method is not required for parasitic inheritance; any function that returns a new object fits the pattern.

image

Keep in mind that adding functions to objects using parasitic inheritance leads to inefficiencies related to function reuse, similar to the constructor pattern.

Parasitic Combination Inheritance

Combination inheritance is the most often-used pattern for inheritance in JavaScript, though it is not without its inefficiencies. The most inefficient part of the pattern is that the supertype constructor is always called twice: once to create the subtype’s prototype, and once inside the subtype constructor. Essentially, the subtype prototype ends up with all of the instance properties of a supertype object, only to have it overwritten when the subtype constructor executes. Consider the combination inheritance example again:

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
                   
SuperType.prototype.sayName = function(){
    alert(this.name);
};
                   
function SubType(name, age){  
    SuperType.call(this, name);          //second call to SuperType()
    
    this.age = age;
}
                   
SubType.prototype = new SuperType();     //first call to SuperType()
SubType.prototype.constructor = SubType;                   
SubType.prototype.sayAge = function(){
    alert(this.age);
};

The highlighted lines of code indicate when SuperType constructor is executed. When this code is executed, SubType.prototype ends up with two properties: name and colors. These are instance properties for SuperType, but they are now on the SubType’s prototype. When the SubType constructor is called, the SuperType constructor is also called, which creates instance properties name and colors on the new object that mask the properties on the prototype. Figure 6-6 illustrates this process.

As you can see, there are two sets of name and colors properties: one on the instance and one on the SubType prototype. This is the result of calling the SuperType constructor twice. Fortunately, there is a way around this.

Parasitic combination inheritance uses constructor stealing to inherit properties but uses a hybrid form of prototype chaining to inherit methods. The basic idea is this: instead of calling the supertype constructor to assign the subtype’s prototype, all you need is a copy of the supertype’s prototype. Essentially, use parasitic inheritance to inherit from the supertype’s prototype and then assign the result to the subtype’s prototype. The basic pattern for parasitic combination inheritance is as follows:

function inheritPrototype(subType, superType){
    var prototype = object(superType.prototype);   //create object
    prototype.constructor = subType;               //augment object
    subType.prototype = prototype;                 //assign object
}

The inheritPrototype() function implements very basic parasitic combination inheritance. This function accepts two arguments: the subtype constructor and the supertype constructor. Inside the function, the first step is to create a clone of the supertype’s prototype. Next, the constructor property is assigned onto prototype to account for losing the default constructor property when the prototype is overwritten. Finally, the subtype’s prototype is assigned to the newly created object. A call to inheritPrototype() can replace the subtype prototype assignment in the previous example, as shown here:

image
function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
                   
SuperType.prototype.sayName = function(){
    alert(this.name);
};
                   
function SubType(name, age){  
    SuperType.call(this, name);
    
    this.age = age;
}
                   
inheritPrototype(SubType, SuperType);
                   
SubType.prototype.sayAge = function(){
    alert(this.age);
};

ParasiticCombinationInheritanceExample01.htm

This example is more efficient in that the SuperType constructor is being called only one time, avoiding having unnecessary and unused properties on SubType.prototype. Furthermore, the prototype chain is kept intact, so both instanceof and isPrototypeOf() behave as they would normally. Parasitic combination inheritance is considered the most optimal inheritance paradigm for reference types.

image

The Yahoo! User Interface (YUI) library was the first to include parasitic combination inheritance in a widely distributed JavaScript library via the Y.extend() method. For more information on YUI, visit http://yuilibrary.com/.

SUMMARY

ECMAScript supports object-oriented (OO) programming without the use of classes or interfaces. Objects are created and augmented at any point during code execution, making objects into dynamic rather than strictly defined entities. In place of classes, the following patterns are used for the creation of objects:

  • The factory pattern uses a simple function that creates an object, assigns properties and methods, and then returns the object. This pattern fell out of favor when the constructor pattern emerged.
  • Using the constructor pattern, it’s possible to define custom reference types that can be created using the new operator in the same way as built-in object instances are created. The constructor pattern does have a downside, however, in that none of its members are reused, including functions. Since functions can be written in a loosely typed manner, there’s no reason they cannot be shared by multiple object instances.
  • The prototype pattern takes this into account, using the constructor’s prototype property to assign properties and methods that should be shared. The combination constructor/prototype pattern uses the constructor to define instance properties and the prototype pattern to define shared properties and methods.

Inheritance in JavaScript is implemented primarily using the concept of prototype chaining. Prototype chaining involves assigning a constructor’s prototype to be an instance of another type. In doing so, the subtype assumes all of the properties and methods of the supertype in a manner similar to class-based inheritance. The problem with prototype chaining is that all of the inherited properties and methods are shared among object instances, making it ill-suited for use on its own. The constructor stealing pattern avoids these issues, calling the supertype’s constructor from inside of the subtype’s constructor. This allows each instance to have its own properties but forces the types to be defined using only the constructor pattern. The most popular pattern of inheritance is combination inheritance, which uses prototype chaining to inherit shared properties and methods and uses constructor stealing to inherit instance properties.

There are also the following alternate inheritance patterns:

  • Prototypal inheritance implements inheritance without the need for predefined constructors, essentially performing a shallow clone operation on a given object. The result of the operation then may be augmented further.
  • Closely related is parasitic inheritance, which is a pattern for creating an object based on another object or some information, augmenting it, and returning it. This pattern has also been repurposed for use with combination inheritance to remove the inefficiencies related to the number of times the supertype constructor is called.
  • Parasitic combination inheritance is considered the most efficient way to implement type-based inheritance.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.220.245.140