Class-based object-oriented languages, such as C++ and Java, are founded upon the concept of two distinct entities -- classes and their instances:
int and
float of C++, it specifies the operations available for its
values -- namely, the methods of the class.NOTE: Avoid the confusion possible between the property
prototype common to all
objects, and the term prototype describing a base object
inherited by other objects.
In class-based languages, you define a class in a class definition. In
that definition, you specify a special method called a constructor, to
initialize instances of the class. A constructor method specifies the initial
values for an instance's variables and performs other processing appropriate
at creation time. You use the new operator together with the
constructor method to allocate the class instances called objects.
JavaScript has no class definition -- just the constructor function itself.
You define the constructor to create objects with an initial set of values.
In class-based languages, constructors are named by their classes, but since
JavaScript is classless, any JavaScript method can be used as a
constructor. And since there is no class to contain properties, they must be
contained in the constructor function's definition. You still use the
new operator with a constructor call to create the new objects.
In summary, class-based languages define objects by classes via constructors, while classless JavaScript defines objects directly by constructors.
In a class-based language, you create a hierarchy of classes through the class
definitions. In a class definition, you can specify that the new class is a
subclass of an existing class. The subclass inherits all the properties
of its superclass and additionally can add further properties or modify the
inherited ones. For example, let class Employee include
properties name and dept, and say class
Manager subclasses Employee, adding property
reports. Then class Manager inherits those properties
of superclass Employee, meaning that any instance of class
Manager would have all three properties: name,
dept, and reports.
JavaScript implements inheritance by associating a prototypical object with
the constructor defining a set of objects. So you can obtain exactly the
Employee-Manager example, though quite differently.
This process requires you to understand a significant difference about
properties between Java and JavaScript:
prototype property, which can be assigned
a prototypical object as value.The following steps explain how to inherit Manager from
Employee:
Employee constructor function, specifying the
name and dept properties as variables.Manager constructor function, specifying the
reports property as a variable.Employee object as the prototype
for the Manager constructor function.Manager, it inherits the name
and dept properties from the Employee object via
prototyping:
function Employee()
{
var name = '', // properties of
dept = 'general' // an Employee
}
function Manager()
{
var reports = [] // empty array property of a Manager
}
Manager.prototype.employee = new Employee() // Manager inherits Employee
Note how Javascript performs inheritance in the reverse order of class-based
languages: their subclass Manager would start with superclass
Employee, then extend by adding property reports,
while JavaScript relates Manager to Employee as
its superclass last.
NOTE: What JavaScript terms inheritance would be considered containment in C++ and Java: without classes, JavaScript objects cannot really inherit the "is-a" functionality, only the "has-a".
In class-based languages, you typically create a class at compile-time and then you instantiate instances of the class either at compile-time or at runtime. You cannot change the number or the type of properties of a class after you define the class.
In JavaScript, however, at runtime you can add or remove properties from any object. If you add a property to an object that is used as the prototype for a set of objects, the objects for which it is the prototype inherit the new property.
The following table gives a short summary of some of these differences. The rest of this chapter describes the details of using JavaScript constructors and prototypes to create an object hierarchy and compares this to how you would do it in Java.
Table 8.1 Comparison of class-based (Java) and prototype-based (JavaScript) object systems
| Class-based (Java) |
Prototype-based (JavaScript)
Classes and objects are present. Only objects -- no classes -- appear. Define a class with a class definition, then instantiate its objects with constructor methods. Define and instantiate objects with constructor functions directly.
Allocate a single object with the Same. Construct an object hierarchy by using class definitions to define subclasses of existing classes. Construct an object hierarchy by assigning an object as the prototype, initialized by a constructor function. Inherit properties by following the class chain. Inherit properties by following the prototype chain. Class definition specifies all properties of all instances of a class. Cannot add or remove properties dynamically at runtime. Constructor function and prototype specifies an initial set of properties. Can add or remove properties dynamically for individual objects or for the entire set of objects. |
|---|
Figure 8.1 A simple object hierarchy
Employee has the properties name (whose value defaults to the empty string) and dept (whose value defaults to "general").Manager is based on Employee. It adds the reports property (whose value defaults to an empty array, intended to have an array of Employee objects as its value).WorkerBee is also based on Employee. It adds the projects property (whose value defaults to an empty array, intended to have an array of strings as its value).SalesPerson is based on WorkerBee. It adds the quota property (whose value defaults to 100). It also overrides the dept property with the value "sales", indicating that all salespersons are in the same d
epartment.Engineer is based on WorkerBee. It adds the machine property (whose value defaults to the empty string) and also overrides the dept property with the value "engineering".
Figure 8.2 The Employee object definitions
Employee definitions are similar. The only differences are that you need to specify the type for each property in Java but not in JavaScript, and you need to create an explicit constructor method for the Java
class.
Manager and WorkerBee definitions show the difference in how to specify the next object higher in the inheritance chain. In JavaScript, you add a prototypical instance as the value of the prototype property of the constructor
function. You can do so at any time after you define the constructor. In Java, you specify the superclass within the class definition. You cannot change the superclass outside the class definition.
Engineer and SalesPerson definitions create objects that descend from WorkerBee and hence from Employee. An object of these types has properties of all the objects above it in the chain. In addition,
these definitions override the inherited value of the dept property with new values specific to these objects.
NOTE: The term instance has a specific technical meaning in class-based languages. In these languages, an instance is an individual member of a class and is fundamentally different from a class. In JavaScript, "instance" does not have this technical meaning because JavaScript does not have this difference between classes and instances. However, in talking about JavaScript, "instance" can be used informally to mean an object created using a particular constructor function. So, in this example, you could informally say thatjaneis an instance ofEngineer. Similarly, although the terms parent, child, ancestor, and descendant do not have formal meanings in JavaScript; you can use them informally to refer to objects higher or lower in the prototype chain.
Figure 8.3 Creating objects with simple definitions
mark object as a WorkerBee as shown in Figure 8.3 with the following statement:
mark = new WorkerBee;When JavaScript sees the
new operator, it creates a new generic object and passes this new object as the value of the this keyword to the WorkerBee constructor function. The constructor function explicitly sets the v
alue of the projects property. It also sets the value of the internal __proto__ property to the value of WorkerBee.prototype. (That property name has two underscore characters at the front and two at the end.) The mark to that object.mark object (local values) for the properties mark inherits from the prototype chain. When you ask for the value of a property, JavaScript first checks to see if the value
exists in that object. If it does, that value is returned. If the value is not there locally, JavaScript checks the prototype chain (using the __proto__ property). If an object in the prototype chain has a value for the property, that value
is returned. If no such property is found, JavaScript says the object does not have the property. In this way, the mark object has the following properties and values:
mark.name = "";The
mark.dept = "general";
mark.projects = [];
mark object inherits values for the name and dept properties from the prototypical object in mark.__proto__. It is assigned a local value for the projects property by the WorkerBee
constructor. This gives you inheritance of properties and their values in JavaScript. Some subtleties of this process are discussed in "Property Inheritance Revisited" on page 138.
Because these constructors do not let you supply instance-specific values, this information is generic. The property values are the default ones shared by all new objects created from WorkerBee. You can, of course, change the values of any of
these properties. So, you could give specific information for mark as follows:
mark.name = "Doe, Mark";
mark.dept = "admin";
mark.projects = ["navigator"];
mark.bonus = 3000;Now, the
mark object has a bonus property, but no other WorkerBee has this property.
If you add a new property to an object that is being used as the prototype for a constructor function, you add that property to all objects that inherit properties from the prototype. For example, you can add a specialty property to all emplo
yees with the following statement:
Employee.prototype.specialty = "none";As soon as JavaScript executes this statement, the
mark object also has the specialty property with the value of "none". The following figure shows the effect of adding this property to the Employee prot
otype and then overriding it for the Engineer prototype.
Figure 8.5 Specifying properties in a constructor, take 1
this.name = name || "";The JavaScript logical OR operator (
||) evaluates its first argument. If that argument converts to true, the operator returns it. Otherwise, the operator returns the value of the second argument. Therefore, this line of code tests to see if <
CODE>name has a useful value for the name property. If it does, it sets this.name to that value. Otherwise, it sets this.name to the empty string. This chapter uses this idiom for brevity; however, it can be p
uzzling at first glance.
With these definitions, when you create an instance of an object, you can specify values for the locally defined properties. As shown in Figure 8.5, you can use the following statement to create a new Engineer:<
/A>
jane = new Engineer("belau");
Jane's properties are now:
jane.name == "";Notice that with these definitions, you cannot specify an initial value for an inherited property such as
jane.dept == "general";
jane.projects == [];
jane.machine == "belau"
name. If you want to specify an initial value for inherited properties in JavaScript, you need to add more code to the constructor funct
ion.
So far, the constructor function has created a generic object and then specified local properties and values for the new object. You can have the constructor add more properties by directly calling the constructor function for an object higher in the prot
otype chain. The following figure shows these new definitions.
Figure 8.6 Specifying properties in a constructor, take 2
Engineer constructor:
function Engineer (name, projs, mach) {
this.base = WorkerBee;
this.base(name, "engineering", projs);
this.machine = mach || "";
}
Suppose you create a new Engineer object as follows:
jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
JavaScript follows these steps:
new operator creates a generic object and sets its __proto__ property to Engineer.prototype.new operator passes the new object to the Engineer constructor as the value of the this keyword.base for that object and assigns the value of the WorkerBee constructor to the base property. This makes the WorkerBee constructor a method of the Engi
neer object.base method, passing as its arguments two of the arguments passed to the constructor ("Doe, Jane" and ["navigator", "javascript"]) and also the string "engineering". Explicitly using "engine
ering" in the constructor indicates that all Engineer objects have the same value for the inherited dept property, and this value overrides the value inherited from Employee.base is a method of Engineer, within the call to base, JavaScript binds the this keyword to the object created in Step 1. Thus, the WorkerBee function
in turn passes the "Doe, Jane" and ["navigator", "javascript"] arguments to the Employee constructor function. Upon return from the Employee constructor function, the WorkerBee function uses
the remaining argument to set the projects property.base method, the Engineer constructor initializes the object's machine property to "belau".jane variable.WorkerBee constructor from inside the Engineer constructor, you have set up inheritance appropriately for Engineer objects. This is not the case. Calling the WorkerBee constructor ensures that an Engineer object starts out with the properties specified in all constructor functions that are called. However, if you later add properties to the Employee or WorkerBee prototypes, th
ose properties are not inherited by the Engineer object. For example, assume you have the following statements:
function Engineer (name, projs, mach) {
this.base = WorkerBee;
this.base(name, "engineering", projs);
this.machine = mach || "";
}
jane = new Engineer("Doe, Jane", ["navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";
The jane object does not inherit the specialty property. You still need to explicitly set up the prototype to ensure dynamic inheritance. Assume instead you have these statements:
function Engineer (name, projs, mach) {
this.base = WorkerBee;
this.base(name, "engineering", projs);
this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;
jane = new Engineer("Doe, Jane", ["
navigator", "javascript"], "belau");
Employee.prototype.specialty = "none";
Now the value of the jane object's specialty property is "none".
__proto__ property).function Employee () {
this.name = "";
this.dept = "general";
}function WorkerBee () {
this.projects = [];
}
WorkerBee.prototype = new Employee;
With these definitions, suppose you create amy as an instance of WorkerBee with the following statement:
amy = new WorkerBee;The
amy object has one local property, projects. The values for the name and dept properties are not local to amy and so are gotten from the amy object's __proto__
property. Thus, amy has these property values:
amy.name == "";Now suppose you change the value of the
amy.dept = "general";
amy.projects == [];
name property in the prototype associated with Employee:
Employee.prototype.name = "Unknown"At first glance, you might expect that new value to propagate down to all the instances of
Employee. However, it does not.
When you create any instance of the Employee object, that instance gets a local value for the name property (the empty string). This means that when you set the WorkerBee prototype by creating a new Empl
oyee object, WorkerBee.prototype has a local value for the name property. Therefore, when JavaScript looks up the name property of the amy object (an instance of WorkerBee), JavaScrip
t finds the local value for that property in WorkerBee.prototype. It therefore does not look farther up the chain to Employee.prototype.
If you want to change the value of an object property at run time and have the new value be inherited by all descendants of the object, you cannot define the property in the object's constructor function. Instead, you add it to the constructor's associate
d prototype. For example, assume you change the preceding code to the following:
function Employee () {
this.dept = "general";
}
Employee.prototype.name = "";function WorkerBee () {
this.projects = [];
}
WorkerBee.prototype = new Employee;<
/A>amy = new WorkerBee;
Employee.prototype.name = "Unknown";In this case, the
name property of amy becomes "Unknown".
As these examples show, if you want to have default values for object properties and you want to be able to change the default values at run time, you should set the properties in the constructor's prototype, not in the constructor function itself.
instanceof operator for this purpose. JavaScript
does not provide instanceof, but you can write such a function yourself.
As discussed in "Inheriting Properties" on page 129, when you use the new operator with a constructor function to create a new object, JavaScript sets the __proto__ property of the new object t
o the value of the prototype property of the constructor function. You can use this to test the prototype chain.
For example, suppose you have the same set of definitions already shown, with the prototypes set appropriately. Create a __proto__ object as follows:
chris = new Engineer("Pigman, Chris", ["jsd"], "fiji");
With this object, the following statements are all true:
chris.__proto__ == Engineer.prototype;Given this, you could write an
chris.__proto__.__proto__ == WorkerBee.prototype;
chris.__proto__.__proto__.__proto__ == Employee.prototype;
chris.__proto__.__proto__.__proto__.__proto__ == Object.prototype;
chris .__proto__.__proto__.__proto__.__proto__.__proto__ == null;
instanceOf function as follows:
function instanceOf(object, constructor) {
while (object != null) {
if (object == constructor.prototype)
return
true;
object = object.__proto__;
}
return false;
}
With this definition, the following expressions are all true:
instanceOf (chris, Engineer)But the following expression is false:
instanceOf (chris, WorkerBee)
instanceOf (chris, Employee)
instanceOf (chris, Object)
instanceOf (chris, SalesPerson)
Employ
ee:
var idCounter = 1;
function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
this.id = idCounter++;
}
With this definition, when you create a new Employee, the constructor assigns it the next ID in sequence and then increments the global ID counter. So, if your next statement is the following, victoria.id is 1 and harry.id<
/CODE> is 2:
victoria = new Employee("Pigbert, Victoria", "pubs")
harry = new Employee("Tschopik, Harry", "sales")
At first glance that seems fine. However, idCounter gets incremented every time an Employee object is created, for whatever purpose. If you create the entire Employee hierarchy shown in this chapter, the Employ
ee constructor is called every time you set up a prototype. Suppose you have the following code:
var idCounter = 1;
function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
this.id = idCounter++;
}function Manager (name, dept, reports) {...}
Manager.prototype = new Employee;function WorkerBee (name, dept, projs) {...}
WorkerBee.prototype = new Employee;function Engineer (name, projs, mach) {...}
Engineer.prototype = new WorkerBee;function SalesPerson (name, projs, quota) {...}
SalesPerson.prototype = new WorkerBee;mac = new Eng
ineer("Wood, Mac");
Further assume that the definitions omitted here have the base property and call the constructor above them in the prototype chain. In this case, by the time the mac object is created, mac.id is 5.
Depending on the application, it may or may not matter that the counter has been incremented these extra times. If you care about the exact value of this counter, one possible solution involves instead using the following constructor:
function Employee (name, dept) {
this.name = name || "";
this.dept = dept || "general";
if (name)
this.id = idCounter++;
}<
/PRE>
When you create an instance of Employee to use as a prototype, you do not supply arguments to the constructor. Using this definition of the constructor, when you do not supply arguments, the constructor does not assign a value to the id and d
oes not update the counter. Therefore, for an Employee to get an assigned id, you must specify a name for the employee. In this example, mac.id would be 1.
No Multiple Inheritance
Some object-oriented languages allow multiple inheritance. That is, an object can inherit the properties and values from unrelated parent objects. JavaScript does not support multiple inheritance.
Inheritance of property values occurs at run time by JavaScript searching the prototype chain of an object to find a value. Because an object has a single associated prototype, JavaScript cannot dynamically inherit from more than one prototype chain.<
/P>
In JavaScript, you can have a constructor function call more than one other constructor function within it. This gives the illusion of multiple inheritance. For example, consider the following statements:
function Hobbyist (hobby) {
this.hobby = hobby || "scuba";
}function Engineer (name, projs, mach, hobby) {
this.base1 = WorkerBee;
th
is.base1(name, "engineering", projs);
this.base2 = Hobbyist;
this.base2(hobby);
this.machine = mach || "";
}
Engineer.prototype = new WorkerBee;dennis =
new Engineer("Doe, Dennis", ["collabra"], "hugo")
Further assume that the definition of WorkerBee is as used earlier in this chapter. In this case, the dennis object has these properties:
dennis.name == "Doe, Dennis"
dennis.dept == "engineering"
dennis.projects == ["collabra"]
dennis.machine == "hugo"
dennis.hobby == "scuba"
So dennis does get the hobby property from the Hobbyist constructor. However, assume you then add a property to the Hobbyist constructor's prototype:
Hobbyist.prototype.equipment = ["mask", "fins", "regulator", "bcd"]
The dennis object does not inherit this new property.
Home Page | Previous
| Top
Last Updated: 05/27/99 21:21:30
Copyright (c) 1999
Netscape Communications Corporation