Arguably the most important Object-Oriented Programming (OOP) language is Java, announced in 1995. Shortly after Java's announcement JavaScript was introduced as a client-side language running in internet browsers. Although its name suggests that it is a version of Java, it is not: JavaScript is an autonomous language. Particularly, JavaScript's handling of objects differs from Java's in that JavaScript's objects inherit properties and methods by means of prototypes, while Java's instances inherit properties from their class.
Javascript's prototype inheritance is rather abstruse and to bolster my understanding of it I wrote these notes. When I wrote them I had knowledge of basic JavaScript and most of the programming constructs that JS shares with other languages. So, the latter topics are not treated below. The exact JavaScript syntax can be found, for instance, here (Guide) or here (Reference). Mainly the version of JavaScript that is known as ecmascript 3 will be used. Later versions of ecmascript are fully compatible with version 3.
The main source for these notes was JavaScript: The Definitive
Guide by David Flanagan, O'Reilly, 4th edition, 2002 (known as the "Rhino
book" because of the Rhinoceros on its cover). Prototype chains and inheritance will
be discussed below in more detail than in the 4th edition of the Rhino book.
The first version of the present notes date from before the
Spring of 2011 when the 6th edition of the Rhino book appeared. The 6th edition
is completely different from the 4th edition, the 6th edition is a new—
and even better—book. It contains much more material explaining
prototype-based inheritance, use of the keyword this, etc.
Although the 6th edition makes the present notes somewhat redundant, they are
not altogether useless, because much of the material below is found scattered
through the 1100 pages of sixth edition of the Rhino book, whereas the present
text concentrates solely on JavaScript's prototype inheritance.
With regard to the format of the present notes: They are written in what used to be called "dynamic html". Also the table of contents is dynamic. This means that part of the text is built by the execution of JS code snippets during browsing. The disadvantage of this is that reading the notes requires a browser that supports JavaScript. The advantage is that no typing or copying errors can creep in.
Code snippets are given internally as follows,
<pre> A code snippet </pre>
<script> The same code snippet </script>
<pre> and </pre> is listed
in the present html text and
the very same code between <script> and </script> is executed with its
output listed in this text in bold face green.
The output of the code snippets is performed by the function:
function println(x) {
document.writeln("<span class='green'>" + x + "</span><br>");
}
function printObj(obj) {
for (key in obj) {
if (obj.hasOwnProperty(key)) println(key + ": " + obj[key]);
}
}
obj.
To keep `for (key in obj)` loops within bounds, many
built-in JavaScript objects have the attribute "nonenumerable". These objects are not listed either.
Most object members in the examples will have simple string values. A string is an immutable sequence of arbitrary characters enclosed by single or double quotes and is easy to assign and recognize. The emphasis of these notes being on prototype inheritance, examples with more complex syntactical features of JavaScript would be distracting.
The full name of a member starts with the name of the object to which
it belongs and is followed by one or more refinements. A refinement is a period
followed by the name of a member, a key (which again may be bound to any JS entity).
This construction of names is as is usual in object-oriented
languages. Long chains of refinements can be found in JavaScript programs.
Usually they describe a hierarchical structure. When grandMa
has a son Joe and Joe has a daughter
Emma, with Emma having the property age (her
age, a natural number), then
grandMa.Joe.Emma.age = 13;
grandMa, Joe, Emma, and age are object keys
and 13 is a number.
JavaScript does not only posses many aspects of OOP languages, but also of functional languages. For instance, the right-hand side of an assignment statement may be a function—a function is handled as any other JS entity (a JS function is a "first class citizen"):
var Constr = function() {};
var declares a variable to be local to a function. In
the context of the present notes all variables outside functions are in global space so that
var
may be omitted, but it is good practice never to forget var.]
JavaScript knows five kinds of primitives, non-objects. Namely: numbers, strings, booleans (true or false), null, undefined. All other JS entities are of type object, characterized by having key-value pairs. It is useful to distinguish different types of objects: functions, arrays, literals, and "object objects" (the latter being non-primitives that are not of type function, array, or literal).
Many
JavaScript objects are defined by application of the operator new
operating on a function. This function is called the
constructor of the object.
Thus, for example, the object named obj may be created as follows:
var obj = new Constr();
Constr, but it can have any name) performs a
role similar to a class definition in other object-oriented languages. In analogy the object
obj on the left-hand side of new is called
an instance of class Constr. It is common practice to
let the names of constructors (operands of new) start with an uppercase
letter and names of other function with a lowercase letter.
In Sec. 5.1 it will be shown how the application of new results
in two kinds of members (properties or methods) of obj.
The first kind are read-only members that are referred to as "inherited".
Members of the the second kind are called "own properties". They
have read-write access and may be are created during the execution of Constr.
new,
there is a more direct way of of creating an object
(which is not a function or array), namely as an object literal. This is simply
a set of key-value pairs that is reminiscent of associative arrays in other languages.
JavaScript does not have associative arrays per se, object literals serve this role.
As an example, the object literal
objLit is defined with two string-valued properties,
named key1 and key2 with values the strings val1 and
val2, respectively:
var objLit = {key1: 'val1',
key2: 'val2'};
printObj(objLit);
key1 and key2 are own (non-inherited)
properties of objLit.
The members of an object may be accessed by an alternative to
the dot notation: the square bracket notation.
A property of an object can be accessed by square brackets
surrounding a quoted key, i.e., objLit["key1"] vs. objLit.key1.
For example,
println('objLit["key1"] has the value ' + '"' + objLit["key1"] + '"');
println('objLit.key1 has the value ' + '"' + objLit.key1 + '"');
+ concatenates strings. ]
To an existing object a method may be added as follows:
objLit.f = function() {println("Hello, world")};
f() is invoked as:
objLit.f()
The statement:
for (var key in object){...}
key. (Therefore the variable name key must not be within
quotes when accessed.)
Many built-in JS objects have long lists of properties that hardly ever change (or do not change at
all if they lack the writable attribute). In order not to clog up the "for-in" loops with these
long lists, many properties carry the nonenumerable attribute, meaning that they are
skipped in "for-in" loops. In later versions of JavaScript this attribute can be set,
in ecmascript 3 this is not possible.
In the following example litObj is
defined and a for-in loop lists its properties. The example
ends with a call to a method of litObj.
The code snippet:
var litObj = {arr: [1,2,3],
fnc: function(s1, s2){println(s1+s2)},
obj: {key1: "X", key2: "Y"}
};
for (var key in litObj){
println(key + ': ' + litObj[key]);
}
var s1 = "Hi ";
var s2 = "there!"
litObj.fnc(s1, s2);
for-in loop in the
body of the function printObj listed in the introduction.
In printObj inherited
objects are filtered out, only own properties are printed.]
var a="XYZ"
and var b=a, it is found that both a and b are
bound to a private copy of the string "XYZ". A change of a's
value does not affect b's value and conversely. This is known
as assignment by value.
Variables with a value that is a compound entity (object, array, or function) are assigned by name. Assignment of a variable to a compound entity gives the variable as value the name of the memory block where the compound value is stored. The variable becomes a pointer to this memory block. Two variables bound to the same compound entity are said to be each other's alias. Aliases share keys and values. The advantage of assignment by name is that it does not require copying or duplication of data, which obviously would require extra memory and extra CPU cycles. Assignment by name is therefore more efficient than assignment by value.
To illustrate changes in aliases, two compound entities objA and objB
are introduced that are each other's alias:
var objA = {a: ['AAA']} // value of objA.a is array with one element
var objB = objA; // objB is an alias of objA (same keys, same values).
hasOwnProperty will be introduced
that distinguishes between own and inherited properties. One expects
an aliased property to be non-inherited (own) and indeed:
println(objB.hasOwnProperty('a'))
objB.a and look at objA.a:
objB.a = ['XYZ']; // reassign element objB.a
println('objA.a == ' + objA.a); // reassignment of objB.a changes objA.a
objA.a did not appear explicitly in an assignment after it
was created, it changed. Likewise, addition of members are seen by all aliases:
objB.b = 123; // New member objB.b
println('objA.b == ' + objA.b); // New member of objA
The same rule holds for arrays. Change in alias B:
var A = [1,2,3];
var B = A;
B.push(4)
println('Array A: ' + A);
A:
When one of the aliases is reconstructed—is bound to another (possibly new)
object—the binding of the others stay to the old (non-reconstructed) object.
As an example, consider 3 aliases objA, objB,
and objC. Reconstruct objB:
var objA = {a: 'I', b: 'II', c: 'III'};
var objB = objA;
var objC = objA;
println('Is objC alias of objB before reconstruction of objB? ' + (objC === objB));
objB = {aa: 'III', bb: 'IV', cc:'V'} // Reconstruct objB
println('Is objC alias of objB after reconstruction of objB? ' + (objC === objB));
println('Is objC alias of objA after reconstruction of objB? ' + (objC === objA));
objB to a new object
aliases are broken. Only objA and objC
remain each others alias, bound to the original object.
Again, the same rule holds for arrays:
var A = [1,2,3];
var B = A; // B and A are aliases
B = [3, 2, 1]; // Reassign B
println('Array A: ' + A);
B gives no change in original array A:
Parenthetically, it is of interest to point out that JavaScript does not know
"free" global variables. Global variables are keys of a
master object, even when this is not apparent. In html
pages read by a webbrowser
the master object is named window. The
declaration var a adds the key a to window.
As an example two variables are created. The second is an explicit
member of window and the first is not.
var a = "AAA"; // Implicit member
window.b = "BBB"; // Explicit member
println('window.a == ' + window.a); // Print as explicit member
println('b == ' + b); // Print as implicit member
a and b are both members of
window. The stem window. may be omitted, it is the default
for global variables.
Aliasing is often employed to give short synonyms to long variable
names. One must be careful in doing this, because aliasing does not work for
primitives. Consider the following example pertaining to a weather station:
var measurements = {station: {temperature: [60, 70, 80],
pressure: [1.0, 1.2, 1.4]}};
var t = measurements.station.temperature; // short-hand alias for an entire array
for (var i=0; i<t.length; i++) {
p = measurements.station.pressure[i]; // short-hand alias for an array element
t[i] = ((t[i] - 32)*5/9).toFixed(1); // From Fahrenheit to Celsius
p = p*1.01325; // From bar to atm
}
// Print the array elements with index 2:
println(measurements.station.temperature[2] + " // updated value");
println(measurements.station.pressure[2] + " // original value");
p does not update
measurements.barometer.pressure[2], because p
has a primitive value (a number) and is not an alias of the array element.
The assignment of the array element t[2] does update
the array measurements.barometer.temperature, a non-primitive.
Primitive and non-primitive values have also different scopes
as function parameters. Consider a function with a primitive parameter o
that is modified inside the function:
var o = 2; //primitive (a number)
var change = function(o){
o = o + 10; // passed by value
}
change(o);
println(o);
o is a one-element
array literal (a non-primitive) and remember that JavaScript starts indexing arrays at 0:
var o = [2]; // o refers to an array of one element
var change = function(o) {
o[0] = o[0] + 10; // passed by name
}
change(o);
println(o);
o makes a difference, it
transforms o from a primitive to an array object and gives o
global scope inside the function change().
new acting on an arbitrarily named function, the constructor.
A function, as any object, has members. By default, all JavaScript
functions receive a member named
prototype at their creation. By default its value is an object. Thus,
for example,
var Constr = function() {};
Constr.prototype.
Because the "prototype member of a constructor function" will be seen to play a pivotal role in
JavaScript's inheritance, we find it convenient to give it the shorter
name "progenitor", although this name is somewhat idiosyncratic as it is not in general use.
As will be shown below
an instance does not necessarily inherit from an object with key prototype
and therefore a separate term seems useful for the object from which an instance inherits properties and methods.
Readers who don't like the term progenitor can substitute prototype and be
aware that a prototype has not always the key prototype.
The prototype member of a function that is not a constructor, although
it exists, does not play any role.
The prototype member only becomes important when the function is a
constructor (appears as an operand of new). In that case all key-value pairs
of the prototype (the progenitor) are inherited by all instances of the constructor. In other words,
all instances have read-access to all members of their progenitor.
For example,
Constr.prototype.age = 43;
var obj = new Constr(); // An instance that inherits from Constr.prototype
println("My age is: " + obj.age)
Constr.prototype is
augmented by a function, the function becomes a method of the instance obj.
Constr.prototype.f = function(){this.str = "XYZ";}; // Add member
obj.f(); // Execute inherited method of instance
println(obj.str ) // Print property of instance
this in the body of the method is a pseudonym of the object
that inherits the method, in this case obj. We will return to
the keyword this in Chapter 7.
new creates an instance by operation on a constructor function
C(). The instance inherits from its progenitor C.prototype.
[Objects created by new
were earlier referred to as "object objects". This duplication in the name will be
skipped from hereon when there is no possibility of confusion with other types of objects.]
Consider the following pseudo-code for a constructor function:
var C = function(args) {
this.a = ...;
this.b = ...;
....
More executable statements
....
}
C.prototype.x = ...;
C.prototype.y = ...;
....
objC, an instance of
a class generated by the constructor C():
var objC = new C(args);
new has three effects:
objC; if objC exists prior to
new, it is overridden.
C(args) and executes the statements inside its body
(using args as arguments). It is during this execution that values are
assigned to this.a, this.b, etc., where this is
replaced by the name of the left-hand side of new, in this case
objC. Own properties of objC are created:
objC.a = ...; objC.b = ...; ....
objC) to its
progenitor C.prototype.
objC to its progenitor is followed when
the inherited properties with keys x, y, ...
are accessed (in read mode): the actual values of C.prototype.x, C.prototype.y, ...
are returned. This is the precise meaning of "inheriting" the properties of the progenitor
C.prototype.
(In Chapter 8 it will be shown that inheriting may not only from "parents", but also
from "grandparents", or even from arbitrary "ancestors"). The property names (keys)
x, y, .. stay the same.
The following rule is central to prototype inheritance.
Any inherited member key of objC points to the equally named
member key of its progenitor:
objC.key → C.prototype.key → Value
Value is an object or primitive.
Because most of the inheritance mechanism occurs behind the scenes, this rule is not
immediately obvious.
In summary:
an instance object created by new has two kind of members: (i) inherited from its progenitor
and (ii) non-inherited (own), created by assignments to this in its constructor or
added later.
Inherited members of an instance are dynamic in the sense that that they point to
the equally named members of its progenitor, even after the latter are created or changed. All
instances of a constructor follow changes in their progenitor during execution.
This will be considered in more detail below.
Object members can be created dynamically. Look, for example, at the method obj.f() in
the
snippet above. The
method f() was added to obj's
progenitor Constr.prototype
after obj was created. This is unlike the relationship
between classes and instances in many class-based OOP
languages where inheritance rules are settled
statically (at compile time, before execution). In view of the fact that inherited
properties of an object can be added and
overridden dynamically through changes in the prototype property of its constructor,
JavaScript is referred to as a prototype-based language rather
than a class-based language.
When an inherited member is overridden, or a new member (key/value pair) is added, the member
becomes an "own property" of the instance:
var C = function() {};
C.prototype.prop = "Abracadabra";
instance_C = new C;
println('"' + instance_C.prop + '" is own? ' + instance_C.hasOwnProperty("prop"));
instance_C.prop = "No magic, please"; // Override inherited property
println('"' + instance_C.prop + '" is own? ' + instance_C.hasOwnProperty("prop"));
All objects have hidden members that are inherited automatically.
JavaScript has a built-in master constructor
function with a prototype from which all objects inherit automatically.
This function is called Object() and members
of Object.prototype are inherited by all JS objects.
For example, all objects inherit the method Object.prototype.hasOwnProperty(arg).
This method returns true or false depending on whether
arg is own or inherited.
An arbitrary object object may be prepared from the built-in master constructor (which, despite its name, is a function):
arb_obj = new Object(); println(typeof arb_obj);
F() entails creation of F.prototype.
Now other object types, besides functions, will be inspected
to see if they receive automatically a member named prototype.
First check an object literal:
var f = {};
if (f.prototype) println('prototype exists'); else println('prototype does not exist');
var f = [];
if (f.prototype) println('prototype exists'); else println('prototype does not exist');
var f = new Object();
if (f.prototype) println('prototype exists'); else println('prototype does not exist');
var f = function(){};
if (f.prototype) println('prototype exists'); else println('prototype does not exist');
prototype of a non-function object is
undefined.
It is possible, of course, to create a property named prototype for any
non-function object, prototype is not a reserved word. This can be done by
adding the key prototype to an object and assigning a value to it. However,
introduction of a prototype
key for a non-function is not a good idea because it may easily give rise to confusion.
C.prototype has an automatic member named constructor.
The value of constructor is the function C() itself:
var C = function() {var x = 'String assigned in body';};
println(C.prototype.constructor);
C.prototype is an object object and that
C.prototype.constructor is a function object. This is done by the JS
operator typeof:
println('C.prototype is of type ' + typeof C.prototype);
println('C.prototype.constructor is of type ' + typeof C.prototype.constructor);
Because JS is a functional language, functions are ordinary JS entities that
can be compared by the operator ===. Hence, the code
var C = function() {var x = 'String assigned in body';};
if (C === C.prototype.constructor) println(true) else println(false);
Let objC be an instance of C().
By inheritance, objC.constructor.prototype exists. Thus, existence of
C.prototype.constructor implies existence of
objC.constructor.prototype. The latter object
has the same value as C.prototype. However, the two objects are not aliases. Among
aliases the relation is democratic: a change in one is a change in all. Inheritance
is intrinsically hierarchical: a change in an instance is not followed
by the same change in its progenitor.
As an illustration, the smallest possible constructor and an instance are defined:
var C = function() {};
var objC = new C();
objC.constructor and
the constructor C are the same:
if (objC.constructor === C) println("true"); else println("false");
if(objC.constructor.prototype === C.prototype)
println("true"); else println("false");
It is evident that a change of a member of an instance will not cause a change
in the corresponding member of the progenitor. If it would, it would bring about
a change in all instances of the progenitor. So, the following example is
probably superfluous: Change an instance and print it together with the corresponding
member of its progenitor:
objC.constructor = 'New';
println('objC.constructor = ' + objC.constructor);
println('C.prototype.constructor = ' + C.prototype.constructor);
objC.constructor has now changed from an inherited to an own property.
Though by default objC.constructor.prototype exists, the existence of
objC.prototype.constructor is unlikely, because objC
is not a function and by default does not posses the member prototype.
The JS rules, such as for example the fact that
objC.constructor.prototype.constructor
and C() are the same, are sometimes dizzying.
The constructor property can be used to determine the type of a variable.
For instance, arrays have the global, built-in, constructor Array()
(a function).
Any array inherits properties from Array.prototype, including the
member constructor, the same as Array().
Thus, if the expression (a.constructor === Array) has
value true, a is an array. Indeed, the snippet:
var a = []; println(a.constructor === Array);
Likewise, functions have the global, built-in, constructor function Function()
and hence:
var f = function() {}
println(f.constructor === Function);
Array and Function
are not formally reserved, it is good programming practice not to assign the names to other
objects, or to rename the corresponding global objects.]
As a first example we add two new members to the progenitor C.prototype
after the instance objC is created:
var C = function() {str = "body of constructor"};
objC = new C();
C.prototype.inheritedString = "funny string";
C.prototype.emptyFunction = function fnc() {};
objC as
is the existing member constructor:
println(objC.constructor); println(objC.inheritedString); println(objC.emptyFunction);
objC inherits properties of its
progenitor C.prototype added after creation of the instance.
The automatic member constructor is not affected by the addition.
In the second example the whole of
C.prototype is replaced by the object literal objLit, again after new
has been executed.
var objLit = {constructor: "This is the new C.prototype.constructor",
inheritedString: "odd string"};
var C = function () {str = "The old constructor"};
C.prototype.old = "old value";
var objC = new C();
C.prototype = objLit; // Replace C.prototype
println("I: Old inherited? " + objC.old);
println("II: The new constructor: " + C.prototype.constructor);
println("III: The inherited constructor: " + objC.constructor);
println("IV: Added member inherited? " + objC.inheritedString)
objC, after replacement of its
progenitor C.prototype, still possesses the properties inherited at
the time new was executed. This means that the value of the original
progenitor C.prototype is preserved somewhere.
Print statement IV shows that a total replacement of the progenitor C.prototype
does not give a new progenitor for existing instances, they inherit from the old
one; C.prototype is no longer the progenitor of objC.
This, too, is a reason why the term "progenitor"
was introduced.
The last example of this section shows the inheritance of the instance
objC when it is created after
C.prototype has obtained its new, nonstandard, properties:
var objLit = {constructor: "This is the new C.prototype.constructor",
inheritedString: "odd string"};
var C = function () {};
C.prototype = objLit; // Replace C.prototype
var objC = new C();
println("I: The inherited constructor: " + objC.constructor);
println("II: Another inherited member: " + objC.inheritedString);
C.prototype is the progenitor of objC.
In summary, objC inherits the actual values of its progenitor.
It follows later changes within the progenitor, but no later total replacements.
Although the following two statements create the same members
of objC, they create different relationships:
var objC = new C();
var objC = C.prototype;
objC
and its progenitor C.prototype, while the second makes objC an alias
of C.prototype, which is a "democratic" relationship.
The next two examples show the difference between the two statements.
In the first example, reassignment of an inherited property does not change
its value in the progenitor:
var C = function(){};
C.prototype.prop = 'AAA';
var objC = new C();
println('objC.prop == ' + objC.prop);
objC.prop = 'BBB';
println('objC.prop == ' + objC.prop);
println('C.prototype.prop == ' + C.prototype.prop);
C.prototype.prop is not changed by a change in objC.prop,
but, of course, the converse is true. The binding between
objC and its progenitor C.prototype is one-directional.
The second example regards the situation that objC and C.prototype
are aliases. It is shown that adding a member to objC
overrides a member with the same name in C.prototype.
var C = function(){};
C.prototype.prop = 'AAA';
println('C.prototype.prop == ' + C.prototype.prop);
var objC = C.prototype; // Sets aliases
objC.prop = 'BBB'; // Override prop
println('C.prototype.prop == ' + C.prototype.prop);
objC and C.prototype
is two-directional and it makes no longer sense to refer to C.prototype
as the progenitor of objC.
this appears in the body of two kinds of functions.
[In JavaScript
embedded in html pages, this may appear in
an event handler in an html
tag; it then refers to name of the tag. This use will not be discussed in the present notes].
The two kinds are:
Fnc is a constructor, an operand of new:
var obj = new Fnc(arg_list);
fnc is a method of an object:
obj.fnc(arg_list);
this is a pseudonym for the left-hand side of
the new statement, the name of the instance that is created.
var C = function() {this.prop = "This in a constructor"};
var objC = new C();
println('"' + objC.prop + '"' + " is an own property of objC? " + objC.hasOwnProperty("prop") );
The name of the object to be created may be composite, i.e., contain refinements.
The composite name is substituted for this. The composite name must
be declared before adding a property to it,
var scientists = {physicists: {}}; // declare object literal yielding composite name
function C(x){
this.name = x;
}
scientists.physicists.famous = new C('Albert Einstein'); // this in C() is pseudonym of left-hand side
println("scientists.physicists.famous.name: " + scientists.physicists.famous.name);
scientists.physicists.famous had existed, it would have been overridden by new.
var obj = {};
obj.nameMethod = function() {};
nameMethod is the name of a
method of object obj. The method is invoked by
obj.nameMethod().
The name of the
object (of which the method is a member) is substituted for this
inside the body of the method. The key of the method is not substituted.
var objC = {};
objC.nameMethod = function(x) { // define method of objC
this.a = x; // binds objC.a to the function argument x
};
objC.nameMethod('XX'); // invoke method
println('objC.a == ' + objC.a); // print new member
When the name of the object is composite, this obtains the full
composite name (without the method name).
In the following example a new key is created and assigned a value
by invocation of the method setName:
var composers = {Austrian: {eightteenthcentury: {famous: {} }}};
composers.Austrian.eightteenthcentury.famous.setName = function(x) {
this.name = x;
};
composers.Austrian.eightteenthcentury.famous.setName("W.A. Mozart")
println("composers.Austrian.eightteenthcentury.famous.name: " + composers.Austrian.eightteenthcentury.famous.name);
The keyword this may appear everywhere in an expression inside the body of a method.
In the next example it is found on the left- and right-hand side of an assignment:
var objC = {
a: 'AAA', // property of objC
meth: function() {this.b = this.a} // method of objC
};
println('objC.b == ' + objC.b); // before invoking objC.method
objC.meth();
println('objC.b == ' + objC.b); // after invoking objC.method
objC.meth) must be
called explicitly to execute its body; prior to its execution the value of
objC.b is undefined.
this is called without context, that is, as an ordinary
non-nested function (not as method or through new) the name of the global
object is substituted for this. This is consistent with the fact
that a free function is seen as a method of the global object.
The host environment of JavaScript is expected to provide such
an object. In html (the markup language of the present notes)
the global object is named window. The name of this object is substituted for
this.
In the following example f() is
neither a constructor nor a method and hence this is a pseudonym for window:
var f = function(x) {
this.a = x;
return 'returns ' + x;
};
println(f('XXX'));
println('window.a == ' + window.a);
println('f == ' + f);
println('window.f == ' + window.f);
f('XXX') sets the value of window.a and returns the
string "returns XXX". The function-valued variable
f() is a method of the global object window; it may be accessed as
f() or as window.f().
this that occurs commonly in JS programs: inside the body of a
function nested within another function. Schematically,
var outer = function() {
var a = "ABCD";
var inner = function( ){
this.b = a;
}
inner();
}
outer();
println(b);
a in the
snippet is an ordinary variable of the outer function and the inner function inner
can access it. The question is: for which object acts this as pseudonym?
Or, what is printed by this snippet, perhaps undefined?
When this is seen as a special variable with a value that depends on context and
one maintains that the only allowed contexts are constructor and method, then the inner
function inner(), being neither a constructor nor a method, does not offer a valid context to
this.
We saw earlier that when the context of this is undefined, the JavaScript standard
prescribes that the name of the global object (window) is substituted. In the snippet above it means
that b becomes a global variable (synonymous to window.b) and that the
snippet prints:
The substitution this→window is made for appearances
of this on the left- and right-hand side of assignments and
leads to the counter-intuitive situation that in a (sometimes deeply) nested function
global variables are manipulated, see the next example. In this example the outer
function is a method of object obj, but the inner function is not a method:
var a = "A global", obj = {};
var obj.outer = function() {
this.a = "A local"; // sets obj.a to "A local"
var inner = function() {
this.b = this.a; // sets window.b to window.a
}
inner(); // invoke inner to set this.b
};
obj.outer(); // invoke outer
println("obj.a: " + obj.a);
println("window.b: " + window.b);
When a function object is nested inside a method and is named by a refinement of this
[such as this.inner() nested inside
obj.outer() in the snippet below],
the nested object is not an inner function, but an inner method. In the snippet
this.inner is assigned at the same level as this.a.
Like this.a, this.inner() is a member (i.c. a method) of
obj. As
always, inside a method this is a pseudonym of the name of the object:
var b = 'Global', obj = {}; // global variable
obj.outer = function() {
this.a = "AAA";
this.inner = function() {this.b = this.a};
this.inner(); // invoke obj.inner() to assign property b
};
obj.outer(); // Invoke method
println('obj.a == ' + obj.a);
println('obj.b == ' + obj.b); // Is property b of obj?
println('b own property of obj? ' + obj.hasOwnProperty("b") );
println('b == ' + b); // Value of global variable
Function(). All methods and properties of
Function.prototype are available to any JS function. Two methods inherited
from Function() are call() and
apply(). The two are closely related.
By means of call() or apply() an arbitrary function can act as a
method of an arbitrary object.
In the statements:
f.call(obj) and f.apply(obj)
f() is called
as if it were a method of obj. This means that in the body of
f() the name obj is substituted everywhere for
this.
The functions call() and apply() differ in the way the arguments
of f() are handled. A list of arguments can be passed to f()
during invocation of f.call. The function
apply
has only two arguments. The first is obj and the second is an argument array.
[See any JavaScript reference
for details of argument arrays.]
The action of call may be mimicked by the creation of
a temporary method m() of obj which is bound to f().
Suppose the function f() has the arguments
f(arg1, arg2, ...)
f.call(obj,arg1, arg2, ...)
obj.m = f; obj.m(arg1, arg2, ...); delete obj.m;
delete].
In the next example, the function f() acts as if it were a method of the
object literal obj. It (i) augments obj with a new property with key
b and value of
obj.a. It (ii) reassigns obj.a
(from its initial value "12345" to the argument "XXX"). It (iii) returns a
value that is assigned to the variable z:
var obj = {a: 12345};
function f(s){
this.b = this.a;
this.a = s;
return s + ' ' + this.a + ' ' + this.b;
}
var z = f.call(obj, 'XXX'); // Both f and obj pre-exist
println("z = " + z);
println("<br>obj after f.call(obj):");
printObj(obj);
Let us summarize the rules.
obj exists prior to execution of f.call(obj, args),
members of obj may be replaced and new members may be added,
exactly as if f would be an instance method called as: obj.f(args).
This case was illustrated in the example above.
obj does not exist prior to execution f.call(obj, ...)
returns an error.
this—the pseudonym of the first argument of call()—does
not appear in f(), the first argument may be omitted: f.call(). Or it may be an
existing variable name: f.call(anyname). The argument anyname is
ignored, but anyname must exist prior to calling or an error occurs.
f() does contain the keyword this, any of
the calls: f.call(), f.call(null), or f.call(window)
execute f() as a method of the master object window, introduced in
chapter 4.
If f has arguments that must be passed via call, the first argument
must be present. Thus, f.call(null, args) is equivalent to f(args);
omission of the first argument, f.call(args) or f.call(, args), gives an error.
f(), invoked by call or apply,
can be of any kind: a global function, a method of a built-in object, a method of a
user-defined object, an instance (inherited) method, and a class method.
We saw that the call f.call(null, args) is equivalent to f(args),
and that f may be an instance method, say f = obj.m.
When the instance method obj.m contains the keyword this
in its body, the following two statements are not equivalent:
obj.m.call(null, args) and obj.m(args)
m as a method of obj and
replaces this by obj. In the
first expression obj.m is executed as a method of the global object
and hence this in the body of obj.m is replaced by window.
As stated above, apply has only two
parameters:
var z = f.apply(obj, arguments);
f is applied.
The second parameter is an argument array. The argument array is unpacked into an argument list by
apply and then apply passes the unpacked list as arguments to
f. Sometimes apply is used solely for unpacking an argument
array.
Class methods are members of the constructor itself; not of its
prototype. When a function handles
instances of class C, it is convenient to
"park" the function as a function-valued member of the constructor
C, rather than in its "default parking spot", the global object window.
A function C.f() is a class method of class C
and is invoked by C.f(...).
As an example the class Vector is introduced. The instances of this class
are 2-dimensional vectors, pairs of real numbers (x, y). They are
represented as objects with keys x and y. The
constructor is:
var Vector = function(xval, yval){ // Constructor for class Vector
this.x = xval;
this.y = yval;
};
var e1 = new Vector(1,0); var e2 = new Vector(0,1);
v1 = (x1, y1), v2 = (x2, y2) ⇒ v3 = v1 + v2 = (x1+x2, y1+y2)
Vector.add, a member of the constructor Vector. The definition of
the class method with its action on two vectors is as follows:
Vector.add = function(v1, v2) {
return new Vector(v1.x + v2.x, v1.y + v2.y);
};
+ is overloaded, it adds numbers and concatenates
strings].
As an example add the two unit vectors e1
and e2:
var v3 = Vector.add(e1,e2); // call the class method by its full name
println("v3 = " + "("+v3.x+","+v3.y+")");
Alternatively, the function add could be written as a method for
a vector instance [as Vector.prototype.add(v)]. The method would be invoked as
v1.add(v2) or v2.add(v1) (one vector to be added to the other).
Because vector addition
treats vectors in a symmetric fashion, a class method in which the vectors appear on equal
footing is more elegant than an instance method—but admittedly this is a matter of taste.
Sometimes it is convenient to create an object that has
another, arbitrary, object, say p, as progenitor, i.e., the object is to inherit
the properties and methods of p. This can be achieved by the
following function:
function Create(p){
function Dummy(){}; // Dummy constructor
Dummy.prototype = p; // Alias Dummy.prototype with p
return new Dummy(); // Return object with p as progenitor
};
Dummy(), which is local to the function, is not returned and is forgotten.
Earlier the built-in master constructor function Object() was introduced.
It is convenient to define the class method create() as a member of
this master constructor:
Object.create = Create; // Create is defined a few lines above.
Object.create()
is built-in automatically in JavaScript versions later than
EcmaScript 3; it is mimicked by the class method in the code snippet above.
In the next example the object heir inherits the property a
and the method meth from object literal progen that
functions as progenitor for heir:
var progen = {a: 123,
meth: function() {println('This method is inherited')}
};
var heir = Object.create(progen);
println("Inherited property heir.a: " + heir.a)
heir.meth(); // Invoke inherited method
progen is an example of a progenitor that is not named prototype.
A progenitor C.prototype can be assigned to obj in two ways:
obj = Object.create(C.prototype) and obj = new C(args).
C.prototype are inherited by obj.
They differ in the fact that during the
operation new C(args) the body of the constructor C(args) is
executed, while C(args) is not executed during the invocation of
Object.create. In the special case that the constructor C()
has an empty body, there is no difference.
Object.prototype; object objects usually have C.prototype,
a member of their constructor C(); functions have Function.prototype;
arrays have Array.prototype. The
only object without a prototype is
Object.prototype.
If upon read access, a key is not found among the own members of the
object, a hidden pointer is followed to the object's progenitor and the key is
sought among the keys of the progenitor. See, e.g., an
illustration
where the progenitor is the prototype member of the constructor.
The hidden
pointer may be set by the action of new, or may be set by default.
In addition, the pointer can be set by the class method
Object.create(p) that creates an object with progenitor
p.
When the progenitor inspected does not posses the key to be retrieved, the key is
searched higher up in the prototype chain. [It
would be consistent to speak of a "progenitor chain", but this deviates too
much from common usage.] The first progenitor inspected, being an object itself,
has also a progenitor, the next progenitor in the prototype chain. If the key sought
is not a key of the second member of the prototype chain either, the search proceeds up the chain
following hidden pointers from progenitor to progenitor. The search must end at a
progenitor that itself does not have a progenitor, which is
Object.prototype. All prototype chains terminate at
Object.prototype. If the desired key is not found at the top, the value
undefined is returned. When the desired key is found below the
top, its value is returned and the search along the prototype chain is
terminated. All keys higher up than the key returned are masked by the
resulting key.
When an invoked method is not an own member of the instance, it is searched higher up
in the chain and invoked
as soon as it is found: the action is delegated to
the higher instance method.
It is important to note that the keyword this
inside the method obtains the name of the instance that started the search—that
delegated the action.
var C = function() {};
var instance = new C();
instance.key → C.prototype.key → Object.prototype.key // Prototype chain
A prototype chain can be extended by inserting a constructor into it; this is known as
subclassing.
A subclass Csub has all inheritable properties of its base class C. Usually a
subclass is introduced to get members in addition to those of the
base class, the subclass members. Subclassing is performed as in the following
very simple example:
var C = function() {}; // Base class constructor
C.prototype.base = "Base property"; // Define example base class property `base`
var Csub = function() {}; // Subclass constructor
Csub.prototype = new C(); // The subclassing statement: Csub.prototype is of class C
Csub.prototype.sub = "Subclass property"; // Define example subclass property `sub`
var objBase = new C(); // Instance of base class C
var objSub = new Csub(); // Instance of subclass Csub
println("objBase.base: " + objBase.base);
println("objBase.sub: " + objBase.sub);
println("objSub.sub: " + objSub.sub);
println("objSub.base: " + objSub.base);
objBase is of class C—the base class—and has
an inherited property with key base and the value "Base property" that is printed.
In the next println statement objBase.sub is accessed, but the key sub is not found, neither as an
own property of objBase, nor in its progenitor C.prototype,
nor in the built-in master Object.property. Therefore the value undefined is returned and printed.
Csub(), being a function, obtains the automatic member
Csub.prototype. This member is overridden by the subclassing statement:
Csub.prototype = new C();. The instance objSub is by definition of class Csub.
When objSub.sub is accessed, the key sub is sought
and found in the lowest progenitor Csub.prototype.
Its value: "Subclass property" is returned and printed.
When objSub.base is accessed,
the key base is not found in its lowest progenitor Csub.prototype, but is found in the
progenitor one higher up, in the progenitor of Csub.prototype,
which is C.prototype. Its value is "Base property".
Hence, the prototype chain for key objSub.base consists of the members:
objSub.base → Csub.prototype.base → C.prototype.base → Object.prototype.base // Prototype chain
The next example shows a longer chain (left to right implies lower to higher)
that defines the search order of the methods of instance f4 at
the bottom of the chain. The following prototype chain will be constructed
(the key x is either a, b, c, or d):
f4.x → F3.prototype.x → F2.prototype.x → F1.prototype.x → Object.prototype.x // Prototype chain
var F1 = function() {};
F1.prototype.a = function() {return '1 AAA';};
F1.prototype.b = function() {return '1 BBB';};
F1.prototype.c = function() {return '1 CCC';};
F1.prototype.d = function() {return '1 DDD';};
var F2 = function() {};
F2.prototype = new F1(); // Subclassing statement
F2.prototype.b = function() {return '2 BBB';};
F2.prototype.c = function() {return '2 CCC';};
F2.prototype.d = function() {return '2 DDD';};
var F3 = function() {};
F3.prototype = new F2(); // Subclassing statement
F3.prototype.c = function() {return '3 CCC';};
F3.prototype.d = function() {return '3 DDD';};
var f4 = new F3(); // Instance f4 of subclass F3
f4.d = function() {return '4 DDD';}; // Assignment
println('f4.a == ' + f4.a() + ' ' + f4.hasOwnProperty("a") );
println('f4.b == ' + f4.b() + ' ' + f4.hasOwnProperty("b") );
println('f4.c == ' + f4.c() + ' ' + f4.hasOwnProperty("c") );
println('f4.d == ' + f4.d() + ' ' + f4.hasOwnProperty("d") );
o.hasOwnProperty("p") returns false if
p is an inherited member of o
and true otherwise.]
d
is assigned to four different object methods. The own (non-inherited) and lowest method
f4.d is retrieved, executed (returning ['4 DDD') and printed.
Similarly, c is assigned
three times, the lowest inherited method f4.c()==F3.prototype.c()=='3
CCC' is retrieved, and so on. For the printing of f4.a()=='1 AAA'
almost the whole chain had to be searched from bottom to top.
The prototype chain is not inspected when a member is accessed in write mode.
Remember,
that writing always creates or updates an own
member. When a member is updated, lower objects with the same key inherit the updated value. In
fact, this was illustrated already in the previous example when F2.prototype.b was
assigned. The
earlier binding of key b to F1.prototype was overridden and replaced
by a binding to F2.prototype. We verify this:
println(F2.prototype.b() + " " + F2.prototype.hasOwnProperty("b"));
Note finally that the prototype chains in the examples above start with the key of an object object. For the keys of other objects, such as functions or arrays, a different rule applies, as will be seen for functions in this chapter.
this was discussed
in different contexts, among them object methods and constructor functions.
For object methods it is irrelevant whether the
method is own or inherited. In both cases
this is a pseudonym for the object.
In the following example the object obj of class Fsub gets two methods:
(i) inheritedMethod, inherited from Fsub, and (ii)
ownMethod, an own method set during execution of new Fsub().
Both methods of obj contain this that will be
shown to be a pseudonym for obj.
var F = function() {
this.inheritedMethod = function(){this.a = 'Inherited'};
}
var Fsub = function() {
this.ownMethod = function(){this.b = 'Own'};
}
Fsub.prototype = new F(); // Subclassing statement sets `Fsub.prototype.inheritedMethod`
var obj = new Fsub(); // sets `ownMethod` (own) and `inheritedMethod` (inherited)
// Verify the types of the methods:
println('obj.inheritedMethod is owned by obj? ' + obj.hasOwnProperty('inheritedMethod'));
println('obj.ownMethod is owned by obj? ' + obj.hasOwnProperty('ownMethod'));
// Invoke the methods
obj.inheritedMethod(); // Invokes inherited method, sets obj.a
obj.ownMethod(); // Invokes own method, sets obj.b
println('obj.a == ' + obj.a);
println('obj.b == ' + obj.b);
new
the prototype of a subclass constructor is made to point to the prototype of a class constructor
higher in the prototype chain. This has the consequence that instances of the subclass inherit
from their ancestor class. In this subsection a more complicated example of subclassing is given.
It is based on a code snippet taken from JSfiddle.
Suppose that a parent class Super exists and that the own and inherited members of
this class match almost perfectly the members of a child class Child. Only
a few minor modifications are required to make Child complete. Maybe
the addition of an extra method, or a modification of an existing method is needed. (In the
example below the method getX of Super is replaced in Child). A constructor
Child() can be defined that in its body executes the constructor
Super(). By this execution Child() creates the same own
members as those of Super.
Furthermore, Child is subclassed to Super by insertion of
Child.prototype in the prototype chain below Super.prototype,
so that the instances of class Child inherit all the members of class Super.
The subclass Child has at this point the very same properties and methods—own and
inherited—as Super.
Once the class Child has been established, the required changes and additions can be
made to it; they do not affect the parent Super at all.
var Super = function(arg) {
/* Constructor function for parent class Super. */
// Set `x` and `prop`, own properties of instances of class Super:
this.x = arg;
this.prop = 'Prop in Super';
}
Super.prototype = {
// `getX` and `get10X` are methods inherited by instances of class `Super`.
getX: function() {
println(this.x);
},
get10X: function() {
println(this.x * 10);
}
}
var Child = function(arg) {
/* Constructor function for class Child. */
Super.call(this, arg); // Execute `Super` as method of `this`.
// ( The statement `child = new Child(..)` below will invoke `Super` with `this`
// equal to `child`. Execution of `Super` sets own properties: `child.x`
// and `child.prop` )
}
// Create `Child.prototype` with progenitor `Super.prototype` (subclassing).
// We do not want to execute `Super()` yet, so we use `Object.create`, rather
// than `new Super()`, as subclassing statement:
Child.prototype = Object.create(Super.prototype);
Child.prototype.getX = function() {
/* Function overrides the method `getX` of class Super. */
println(this.x * 2);
}
// In the next statement Child() is executed with `this` equal to `child`;
// `Super()` is invoked during execution:
var child = new Child(5); // Create instance of class Child
// Note that the own properties `child.prop` and `child.x` are set
// during execution of Child().
println('child.prop: ' + child.prop);
println('child.x: ' + child.x);
// The inherited method `child.get10X()` is set by the subclassing statement
// `Object.create(..)`. Invoke it:
child.get10X(); // prints 50
// Confirm heritage statuses:
println('prop own property of child? ' + child.hasOwnProperty('prop'));
println('getX own property of child? ' + child.hasOwnProperty('getX'));
println('get10X own property of child? ' + child.hasOwnProperty('get10X'));
// Invoke overridden method:
child.getX(); // prints 10
var Super = function(arg) {
this.x = arg;
this.prop = 'Prop in Super';
}
Super.prototype = {
getX: function() {
println(this.x);
},
get10X: function() {
println(this.x * 10);
}
}
var Child = function(arg) {
Super.call(this, arg);
}
Child.prototype = Object.create(Super.prototype);
Child.prototype.getX = function() {
println(this.x * 2);
}
var child = new Child(5);
child.get10X();
child.getX();
Function(). One can, for example, define a function as follows:
var f = new Function('x', 'return x*x;');
println(f(3.0));
Function(), being a function, has the member
prototype. The function definition via new clearly
sets out that the function f—seen as object—inherits
from Function.prototype. For instance, the methods
call() and apply(), introduced earlier,
are members of Function.prototype.
new to Function(..)
is completely equivalent to the more compact form:
var f = function(x) {return x*x;};
println(f(3.0));
f defined in this—more common—way also has
Function() as its constructor.
Function.prototype, being an object, has as ultimate progenitor
Object.prototype. Functions inherit first from
Function.prototype and after that from Object.prototype.
The shortest [below it will shown to be the only] prototype
chain associated with a function object f() is:
f.key → Function.prototype.key → Object.prototype.key // Prototype chain
var f = function f {};
f.a = 'A';
Function.prototype.a = 'AA';
Function.prototype.b = 'BB';
Object.prototype.a = 'AAA';
Object.prototype.b = 'BBB';
Object.prototype.c = 'CCC';
println('f.a == ' + f.a);
println('f.b == ' + f.b);
println('f.c == ' + f.c);
println('f.d == ' + f.d);
a is an own property of f; f.a masks
Function.prototype.a and Object.prototype.a
f.b is inherited from Function.prototype. The key
Function.prototype.b masks Object.prototype.b.
f.c is inherited from Object.prototype.
f.d cannot be found in the prototype chain. The value
undefined result is returned.
The following example shows that a member of an object is searched
for in Function.prototype if and only if the object is a function.
Object.prototype.key = "Inherited from Object"
Function.prototype.key = "Inherited from Function";
var o = function() {}; // function
var p = []; // array
var q = {}; // object literal
q.f = function() {}; // method (a function)
var s = new String('XYZ'); // string object
println("o.key == " + o.key);
println("o.prototype.key == " + o.prototype.key);
println("p.key == " + p.key);
println("q.key == " + q.key);
println("q.f.key == " + q.f.key);
println("s.key == " + s.key);
Since Object() and Function() are both functions, their properties
are also searched among the properties of Function.prototype that masks
Object.prototype. Example,
Object.prototype.k = 'Object k';
Object.prototype.l = 'Object l';
Function.prototype.k = 'Function';
println('Function.k == ' + Function.k);
println('Function.l == ' + Function.l);
println('Object.k == ' + Object.k);
println('Object.l == ' + Object.l);
Function.prototype, the object Object
inherits from Object.prototype.
It was shown earlier
that a prototype chain starting with an object object could become part
of a prototype chain of arbitrary length by repeated subclassing. Now the
question arises: can a function be the start of a prototype chain of arbitrary length? Or stated
equivalently, can the class Function be subclassed? The answer is no. The different ways of
constructing a function all use Function() as constructor. It is not possible to
use another function constructor. In contrast to object objects, where any function can act
as a constructor, JavaScript knows only one function constructor.
Also arrays, object literals, numbers, regular expressions, dates, booleans, and string objects have
one constructor only, namely the respective built-in functions:
Array(), Object(), Number(), RegExp(),
Date(), Boolean(), and String().
The object Function.prototype has automatically the methods call(), apply(),
bind(), and toString(). Sometimes it may be
useful, since subclassing of functions is not possible, to define
Function.prototype.newMethod(); newMethod() is then
accessible by any function or method.
Object.create(). This method is used in "factory
functions"—by definition functions that return an object. The object
returned from a factory function inherits from an object that acts as a progenitor.
Remember that the statement
var child = Object.create(base);
base to the newly created object
child. The object base is the progenitor of child,
the methods and properties of base are inherited by child.
If child is preexisting, it is overridden. The objects base
and child are associated in the asymmetric one-way manner that is typical for inheritance.
That is, when a member of base is modified or a member is added, the object child
follows these changes. Conversely, changes in child do not affect
base, but create own members of child.
We know turn to an example of a factory function, which, as stated,
returns an object. An array object points is constructed consisting
of a pair of points on the real line; the array is produced by the function
makePoints.
Two methods will be defined. First, between(x) that returns true if x lies
between
the two points. And secondly, distance that gives the distance between the points.
The methods will be members of the progenitor object. Not only can the progenitor be
named anything, it can be "parked" everywhere. It makes sense to park it as a member of the
factory function itself and name it "methods", because it is an object with
two methods: between and distance.
function makePoints(p1, p2) { // factory function
var points = Object.create(makePoints.methods);
points[1] = p1;
points[2] = p2;
return points; // pair of points, an array object
};
// Progenitor object containing methods for points, property of function makePoints:
makePoints.methods = {
between: function(x) {
if (this[1] <= this[2]) {
return (this[1] <= x && x <= this[2])
}
else {
return (this[2] <= x && x <= this[1]);
}
},
distance: function() {
return Math.abs(this[1] - this[2]);
}
}
// Invoke the factory function:
var points = makePoints(3, -1);
println('Points: ' + points[1] + ', ' + points[2]);
// Use its methods
println('0 in interval? ' + points.between(0));
println('3.1 in interval? ' + points.between(3.1));
println('-5.0 in interval? ' + points.between(-5.0));
println('distance: ' + points.distance(4));
The output of the example is: object.key. All JavaScript variable names are keys of well-defined objects,
even when the object is not shown explicitly. Global variables are keys of
the global object that in web pages has the name window.
The binding of a key to an object can be either a one-way or a two-way binding.
One-way binding is also referred to as inheritance.
The binding can be achieved in two ways:
var heir = new Constructor();
var heir = Object.create(Constructor.prototype);
heir is created and its keys and values
are the same as those of the progenitor Constructor.prototype. These inherited
members of heir are bound to
equally named members of the progenitor, but the converse is not true. Changes in the properties of
Constructor.prototype affect dynamically (at run-time) the inherited
properties of heir. Changes of inherited properties of heir do
not affect the properties of progenitor. An object may have
non-inherited—so-called own—properties. An inherited property becomes an
own property when its key is reassigned to any value in an assignment statement.
Two-way binding is by direct assignment:
var objectA = objectB;
objectA and
objectB may be refinements. For instance,
objectA could be of the form grandParent.parent.child.
A JavaScript function can be invoked as in most languages; it causes execution of the
statements in the function body. In addition, a JavaScript function is an object
with keys and their values. The most important member of a constructor function is
prototype.