
Test Driven JavaScript Development- P7
Mô tả tài liệu
Test Driven JavaScript Development- P7:This book is about programming JavaScript for the real world, using the techniques and workflow suggested by Test-Driven Development. It is about gaining confidence in your code through test coverage, and gaining the ability to fearlessly refactor and organically evolve your code base. It is about writing modular and testable code. It is about writing JavaScript that works in a wide variety of environments and that doesn’t get in your user’s way.
Tóm tắt nội dung
ptg
6.4 Memoization 113
Listing 6.26 Memoizing the Fibonacci sequence in a closure
var fibonacci = (function () {
var cache = {};
function fibonacci(x) {
if (x < 2) {
return 1;
}
if (!cache[x]) {
cache[x] = fibonacci(x - 1) + fibonacci(x - 2);
}
return cache[x];
}
return fibonacci;
}());
This alternative version of fibonacci runs many orders of magnitude faster
than the original one, and by extension is capable of calculating more numbers in
the sequence. However, mixing computation with caching logic is a bit ugly. Again,
we will add a function to Function.prototype to help separate concerns.
The memoize method in Listing 6.27 is capable of wrapping a method, adding
memoization without cluttering the calculation logic.
Listing 6.27 A general purpose memoize method
if (!Function.prototype.memoize) {
Function.prototype.memoize = function () {
var cache = {};
var func = this;
return function (x) {
if (!(x in cache)) {
cache[x] = func.call(this, x);
}
return cache[x];
};
};
}
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
114 Applied Functions and Closures
This method offers a clean way to memoize functions, as seen in Listing 6.28.
Listing 6.28 Memoizing the fibonacci function
TestCase("FibonacciTest", {
"test calculate high fib value with memoization":
function () {
var fibonacciFast = fibonacci.memoize();
assertEquals(1346269, fibonacciFast(30));
}
});
The memoizemethod offers a clean solution but unfortunately only deals with
functions that take a single argument. Limiting its use further is the fact that it
blindly coerces all arguments to strings, by nature of property assignment, which
will be discussed in detail in Chapter 7, Objects and Prototypal Inheritance.
To improve the memoizer, we would need to serialize all arguments to use as
keys. One way to do this, which is only slightly more complex than what we already
have, is to simply join the arguments, as Listing 6.29 does.
Listing 6.29 A slightly better memoize method
if (!Function.prototype.memoize) {
Function.prototype.memoize = function () {
var cache = {};
var func = this;
var join = Array.prototype.join;
return function () {
var key = join.call(arguments);
if (!(key in cache)) {
cache[key] = func.apply(this, arguments);
}
return cache[key];
};
};
}
This version will not perform as well as the previous incarnation because it both
calls join and uses apply rather than call, because we no longer can assume
the number of arguments. Also, this version will coerce all arguments to strings
as before, meaning it cannot differentiate between, e.g., "12" and 12 passed as
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
6.5 Summary 115
arguments. Finally, because the cache key is generated by joining the parameters
with a comma, string arguments that contain commas can cause the wrong value to
be loaded, i.e., (1, "b") would generate the same cache key as ("1,b").
It is possible to implement a proper serializer that can embed type information
about arguments, and possibly use tddjs.uid to serialize object and function
arguments, but doing so would impact the performance ofmemoize in a noticeable
way such that it would only help out in cases that could presumably be better
optimized in other ways. Besides, serializing object arguments using tddjs.uid,
although simple and fast, would cause the method to possibly assign new properties
to arguments. That would be unexpected in most cases and should at the very least
be properly documented.
6.5 Summary
In this chapter we have worked through a handful of practical function examples
with a special focus on closures. With an understanding of the scope chain from
Chapter 5, Functions, we have seen how inner functions can keep private state in
free variables. Through examples we have seen how to make use of the scope and
state offered by closures to solve a range of problems in an elegant way.
Some of the functions developed in this chapter will make appearances in
upcoming chapters as we build on top of them and add more useful interfaces to
the tddjs object. Throughout the book we will also meet plenty more examples
of using closures.
In the next chapter we will take a look at JavaScript’s objects and gain a bet-
ter understanding of how property access and prototypal inheritance work, how
closures can help in object oriented programming in JavaScript, as well as explore
different ways to create objects and share behavior between them.
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
This page intentionally left blank
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
7Objects and PrototypalInheritance
JavaScript is an object oriented programming language. However, unlike most
other object oriented languages, JavaScript does not have classes. Instead, JavaScript
offers prototypes and prototype-based inheritance in which objects inherit from other
objects. Additionally, the language offers constructors—functions that create ob-
jects, a fact that often confuses programmers and hides its nature. In this chapter
we’ll investigate how JavaScript objects and properties work. We’ll also study the
prototype chain as well as inheritance, working through several examples in a test-
driven manner.
7.1 Objects and Properties
JavaScript has object literals, i.e., objects can be typed directly into a program using
specific syntax, much like string ("a string literal") and number literals
(42) can be typed directly in a program in most languages. Listing 7.1 shows an
example of an object literal.
Listing 7.1 An object literal
var car = {
model: {
year: "1998",
make: "Ford",
model: "Mondeo"
117
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
118 Objects and Prototypal Inheritance
},
color: "Red",
seats: 5,
doors: 5,
accessories: ["Air condition", "Electric Windows"],
drive: function () {
console.log("Vroooom!");
}
};
Incidentally, Listing 7.1 shows a few other literals available in JavaScript as well,
most notably the array literal ([] as opposed to new Array()).
ECMA-262 defines a JavaScript object as an unordered collection of properties.
Properties consist of a name, a value, and a set of attributes. Property names are
either string literals, number literals, or identifiers. Properties may take any value,
i.e., primitives (strings, numbers, booleans, null or undefined) and objects,
including functions. When properties have function objects assigned to them, we
usually refer to them as methods. ECMA-262 also defines a set of internal proper-
ties and methods that are not part of the language, but are used internally by the
implementation. The specification encloses names of these internal properties and
methods in double brackets, i.e., [[Prototype]]. I will use this notation as well.
7.1.1 Property Access
JavaScript properties can be accessed in one of two ways—using dot notation,
car.model.year, or using a style commonly associated with dictionaries or
hashes, car["model"]["year"]. The square bracket notation offers a great
deal of flexibility when looking up properties. It can take any string or expression
that returns a string. This means that you can dynamically figure out the property
name at run-time and look it up on an object directly using the square brackets.
Another benefit of the square bracket notation is that you can access properties
whose name contain characters not allowed in identifiers such as white space. You
can mix dot and bracket notation at will, making it very easy to dynamically look
up properties on an object.
As you might remember, we used property names containing spaces to make
our test case names more human-readable in Chapter 3, Tools of the Trade, as seen in
Listing 7.2.
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
7.1 Objects and Properties 119
Listing 7.2 A property name with spaces
var testMethods = {
"test dots and brackets should behave identically":
function () {
var value = "value";
var obj = { prop: value };
assertEquals(obj.prop, obj["prop"]);
}
};
// Grab the test
var name = "test dots and brackets should behave identically";
var testMethod = testMethods[name];
// Mix dot and bracket notation to get number of expected
// arguments for the test method
var argc = testMethods[name].length;
Here we get a test method (i.e., a property) from our object using the square
bracket notation, because the name of the property we are interested in contains
characters that are illegal in identifiers.
It is possible to get and set properties on an object using other values than
string literals, number literals, or identifiers. When you do so, the object will be
converted to a string by its toString method if it exists (and returns a string), or
its valueOf method. Beware that these methods may be implementation-specific
(e.g., for host objects1), and for generic objects the toString method will return
"[object Object]". I recommend you stick to identifiers, string literals, and
number literals for property names.
7.1.2 The Prototype Chain
In JavaScript every object has a prototype. The property is internal and is referred
to as [[Prototype]] in the ECMA-262 specification. It is an implicit reference to the
prototypeproperty of the constructor that created the object. For generic objects
this corresponds to Object.prototype. The prototype may have a prototype of
its own and so on, forming a prototype chain. The prototype chain is used to share
properties across objects in JavaScript, and forms the basis for JavaScript’s inheri-
tance model. This concept is fundamentally different from classical inheritance, in
1. Host objects will be discussed in Chapter 10, Feature Detection.
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
120 Objects and Prototypal Inheritance
which classes inherit from other classes, and objects constitute instances of classes.
We’ll approach the subject by continuing our study of property access.
When you read a property on an object, JavaScript uses the object’s internal
[[Get]] method. This method checks if the object has a property of the given
name. If it has, its value is returned. If the object does not have such a property,
the interpreter checks if the object has a [[Prototype]] that is not null (only
Object.prototype has a null [[Prototype]]). If it does, the interpreter will
check whether the prototype has the property in question. If it does, its value is
returned, otherwise the interpreter continues up the prototype chain until it reaches
Object.prototype. If neither the object nor any of the objects in its prototype
has a property of the given name, undefined is returned.
When you assign, or put, a value to an object property, the object’s internal
[[Put]] method is used. If the object does not already have a property of the given
name, one is created and its value is set to the provided value. If the object already
has a property of the same name, its value is set to the one provided.
Assignment does not affect the prototype chain. In fact, if we assign a prop-
erty that already exists on the prototype chain, we are shadowing the prototype’s
property. Listing 7.3 shows an example of property shadowing. To run the test with
JsTestDriver, set up a simple project as described in Chapter 3, Tools of the Trade,
and add a configuration file that loads test/*.js.
Listing 7.3 Inheriting and shadowing properties
TestCase("ObjectPropertyTest", {
"test setting property shadows property on prototype":
function () {
var object1 = {};
var object2 = {};
// Both objects inherit Object.prototype.toString
assertEquals(object1.toString, object2.toString);
var chris = {
name: "Chris",
toString: function () {
return this.name;
}
};
// chris object defines a toString property that is
// not the same object as object1 inherits from
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
7.1 Objects and Properties 121
// Object.prototype
assertFalse(object1.toString === chris.toString);
// Deleting the custom property unshadows the
// inherited Object.prototype.toString
delete chris.toString;
assertEquals(object1.toString, chris.toString);
}
});
As seen in Listing 7.3, object1 and object2 don’t define a toString
property and so they share the same object—the Object.prototype.
toString method—via the prototype chain. The chris object, on the other
hand, defines its own method, shadowing the toString property on the
prototype chain. If we delete the custom toString property from the chris
object using the delete operator, the property no longer exists directly on the
specific object, causing the interpreter to look up the method from the prototype
chain, eventually finding Object.prototype.
When we turn our attention to property attributes, we will discuss some addi-
tional subtleties of the [[Put]] method.
7.1.3 Extending Objects through the Prototype Chain
By manipulating the prototype property of JavaScript constructors we can mod-
ify the behavior of every object created by it, including objects created before the
manipulation. This also holds for native objects, such as arrays. To see how this
works, we’re going to implement a simple sum method for arrays. The test in
Listing 7.4 illustrates what we want to achieve.
Listing 7.4 Describing the behavior of Array.prototype.sum
TestCase("ArraySumTest", {
"test should summarize numbers in array": function () {
var array = [1, 2, 3, 4, 5, 6];
assertEquals(21, array.sum());
}
});
Running this test informs us that there is no sum method for arrays, which is
not all that surprising. The implementation is a trivial summarizing loop, as seen in
Listing 7.5.
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
122 Objects and Prototypal Inheritance
Listing 7.5 Adding a method to Array.prototype
Array.prototype.sum = function () {
var sum = 0;
for (var i = 0, l = this.length; i < l; i++) {
sum += this[i];
}
return sum;
};
Because all arrays inherit from Array.prototype, we’re able to add
methods to all arrays. But what happens if there already is a sum method for
arrays? Such a method could be provided by a given browser, a library or other
code running along with ours. If this is the case, we’re effectively overwriting that
other method. Listing 7.6 avoids this by placing our implementation inside an if
test that verifies that the method we’re adding does not already exist.
Listing 7.6 Defensively adding a method to Array.prototype
if (typeof Array.prototype.sum == "undefined") {
Array.prototype.sum = function () {
// ...
};
}
In general, this is a good idea when extending native objects or otherwise
working on global objects. This way we make sure our code doesn’t trip up other
code. Even so, if there already is a sum method available, it may not act the way we
expect, causing our code that relies on our sum to break. We can catch these errors
with a strong test suite, but this kind of problem clearly indicates that relying on
extensions to global objects may not be the best approach when the focus is writing
robust code.
7.1.4 Enumerable Properties
Extending native prototypes like we just did comes with a price. We already saw
how this may lead to conflicts, but there is another drawback to this approach.
When adding properties to an object, they are instantly enumerable on any instance
that inherits it. Listing 7.7 shows an example of looping arrays.
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
7.1 Objects and Properties 123
Listing 7.7 Looping arrays with for and for-in
TestCase("ArrayLoopTest", {
"test looping should iterate over all items":
function () {
var array = [1, 2, 3, 4, 5, 6];
var result = [];
// Standard for-loop
for (var i = 0, l = array.length; i < l; i++) {
result.push(array[i]);
}
assertEquals("123456", result.join(""));
},
"test for-in loop should iterate over all items":
function () {
var array = [1, 2, 3, 4, 5, 6];
var result = [];
for (var i in array) {
result.push(array[i]);
}
assertEquals("123456", result.join(""));
}
});
These two loops both attempt to copy all the elements of one array onto another,
and then join both arrays into a string to verify that they do indeed contain the same
elements. Running this test reveals that the second test fails with the message in
Listing 7.8.
Listing 7.8 Result of running test in Listing 7.7
expected "123456" but was "123456function () { [... snip]"
To understand what’s happening, we need to understand the for-in enu-
meration. for (var property in object) will fetch the first enumerable
property of object. property is assigned the name of the property, and the
body of the loop is executed. This is repeated as long as object has more enu-
merable properties, and the body of the loop does not issue break (or return if
inside a function).
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
124 Objects and Prototypal Inheritance
For an array object, the only enumerable properties are its numeric indexes.
The methods and the length property provided by Array.prototype are not
enumerable. This is why a for-in loop will only reveal the indexes and their
associated values for array objects. However, when we add properties to an object
or one of the objects in its prototype chain, they are enumerable by default. Because
of this fact, these new properties will also appear in a for-in loop, as shown by
the test failure above.
I recommend you don’t use for-in on arrays. The problem illustrated above
can be worked around, as we will see shortly, but not without trading off per-
formance. Using for-in on arrays effectively means we can’t normalize browser
behavior by adding missing methods to Array.prototype without inferring a
performance hit.
7.1.4.1 Object.prototype.hasOwnProperty
Object.prototype.hasOwnProperty(name) returns true if an object
has a property with the given name. If the object either inherits the property from
the prototype chain, or doesn’t have such a property at all, hasOwnProperty
returns false. This means that we can qualify a for-in loop with a call to
hasOwnProperty to ensure we only loop the object’s own properties, as seen in
Listing 7.9.
Listing 7.9 Qualifying a loop with hasOwnProperty
"test looping should only iterate over own properties":
function () {
var person = {
name: "Christian",
profession: "Programmer",
location: "Norway"
};
var result = [];
for (var prop in person) {
if (person.hasOwnProperty(prop)) {
result.push(prop);
}
}
var expected = ["location", "name", "profession"];
assertEquals(expected, result.sort());
}
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
7.1 Objects and Properties 125
This test passes because we now filter out properties added to the proto-
type chain. There are two things to keep in mind when dealing with Object.
prototype.hasOwnProperty.
• Some browsers, such as early versions of Safari don’t support it.
• Objects are frequently used as hashes; there is a risk of hasOwnProperty
being shadowed by another property.
To guard our code against the latter case, we can implement a custom method
that accepts an object and a property and returns true if the property is one of
the object’s own properties, even when the object’s hasOwnProperty method is
shadowed or otherwise unavailable. Listing 7.10 shows the method. Add it to the
tdd.js file from Chapter 6, Applied Functions and Closures.
Listing 7.10 Sandboxed hasOwnProperty
tddjs.isOwnProperty = (function () {
var hasOwn = Object.prototype.hasOwnProperty;
if (typeof hasOwn == "function") {
return function (object, property) {
return hasOwn.call(object, property);
};
} else {
// Provide an emulation if you can live with possibly
// inaccurate results
}
}());
For browsers that do not support this method we can emulate it, but it is not
possible to provide a fully conforming implementation. Chances are that browsers
that lack this method will present other issues as well, so failing to provide an
emulation may not be your main problem. We will learn techniques to deal with
such cases in Chapter 10, Feature Detection.
Because properties are always enumerable when added by JavaScript, and
because globals make it hard for scripts to co-exist, it is widely accepted that
Object.prototype should be left alone. Array.prototype should also be
treated with care, especially if you are usingfor-in on arrays. Although such loops
should generally be avoided for arrays, they can be useful when dealing with large,
sparse arrays.
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
126 Objects and Prototypal Inheritance
Keep in mind that although you may decide to avoid extending native objects,
others may not be so nice. Filtering for-in loops with hasOwnProperty—even
when you are not modifying Object.prototype and Array.prototype
yourself—will keep your code running as expected, regardless of whether third-
party code such as libraries, ad, or analytics related code decide to do so.
7.1.5 Property Attributes
ECMA-262 defines four properties that may be set for any given property. It
is important to note that these attributes are set for properties by the inter-
preter, but JavaScript code you write has no way of setting these attributes.
The ReadOnly and DontDelete attributes cannot be inspected explicitly, but
we can deduce their values. ECMA-262 specifies the Object.prototype.
propertyIsEnumerable method, which could be used to get the value of
DontEnum; however, it does not check the prototype chain and is not reliably
implemented across browsers.
7.1.5.1 ReadOnly
If a property has the ReadOnly attribute set, it is not possible to write to the pro-
perty. Attempting to do so will pass by silently, but the property attempted to update
will not change. Note that if any object on the prototype chain has a property with
the attribute set, writing to the property will fail. ReadOnly does not imply that the
value is constant and unchanging—the interpreter may change its value internally.
7.1.5.2 DontDelete
If a property has the DontDelete attribute set, it is not possible to delete it
using the delete operator. Much like writing to properties with the ReadOnly
attribute, deleting properties with the DontDelete attribute will fail silently. The
expression will return false if the object either didn’t have the given property, or if
the property existed and had a DontDelete attribute.
7.1.5.3 DontEnum
DontEnum causes properties to not appear in for-in loops, as shown in
Listing 7.9. The DontEnum attribute is the most important property attribute
to understand because it is the one that is most likely to affect your code. In
Listing 7.7 we saw an example of how enumerable properties may trip up badly
written for-in loops. The DontEnum attribute is the internal mechanism that
decides whether or not a property is enumerable.
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
7.1 Objects and Properties 127
Internet Explorer (including version 8) has a peculiar bug concerning the
DontEnum attribute—any property on an object that has a property by the same
name anywhere on its prototype chain that has DontEnum will act as though it has
DontEnum as well (even though it should be impossible to have DontEnum on a
user-provided object). This means that if you create an object and shadow any of the
properties on Object.prototype, neither of these properties will show up in a
for-in loop in Internet Explorer. If you create an object of any of the native and
host types, all the properties on the respective prototype chains with DontEnum
will magically disappear from a for-in loop, as seen in Listing 7.11.
Listing 7.11 Overriding properties with DontEnum
TestCase("PropertyEnumerationTest", {
"test should enumerate shadowed object properties":
function () {
var object = {
// Properties with DontEnum on Object.prototype
toString: "toString",
toLocaleString: "toLocaleString",
valueOf: "valueOf",
hasOwnProperty: "hasOwnProperty",
isPrototypeOf: "isPrototypeOf",
propertyIsEnumerable: "propertyIsEnumerable",
constructor: "constructor"
};
var result = [];
for (var property in object) {
result.push(property);
}
assertEquals(7, result.length);
},
"test should enumerate shadowed function properties":
function () {
var object = function () {};
// Additional properties with DontEnum on
//Function.prototype
object.prototype = "prototype";
object.call = "call";
object.apply = "apply";
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
128 Objects and Prototypal Inheritance
var result = [];
for (var property in object) {
result.push(property);
}
assertEquals(3, result.length);
}
});
Both of these tests fail in all versions of Internet Explorer, including IE8;
result.length is 0. We can solve this issue by making a special case for
the non-enumerable properties on Object.prototype as well as Function.
prototype if the object in question inherits from it.
The tddjs.eachmethod in Listing 7.12 can be used to loop properties of an
object, accounting for Internet Explorer’s bug. When defined, the method attempts
to loop the properties of an object that shadows all the non-enumerable proper-
ties on Object.prototype as well as a function that shadows non-enumerable
properties on Function.prototype. Any property that does not show up in
the loop is remembered and looped explicitly inside the each function.
Listing 7.12 Looping properties with a cross-browser each method
tddjs.each = (function () {
// Returns an array of properties that are not exposed
// in a for-in loop on the provided object
function unEnumerated(object, properties) {
var length = properties.length;
for (var i = 0; i < length; i++) {
object[properties[i]] = true;
}
var enumerated = length;
for (var prop in object) {
if (tddjs.isOwnProperty(object, prop)) {
enumerated -= 1;
object[prop] = false;
}
}
if (!enumerated) {
return;
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
7.1 Objects and Properties 129
}
var needsFix = [];
for (i = 0; i < length; i++) {
if (object[properties[i]]) {
needsFix.push(properties[i]);
}
}
return needsFix;
}
var oFixes = unEnumerated({},
["toString", "toLocaleString", "valueOf",
"hasOwnProperty", "isPrototypeOf",
"constructor", "propertyIsEnumerable"]);
var fFixes = unEnumerated(
function () {}, ["call", "apply", "prototype"]);
if (fFixes && oFixes) {
fFixes = oFixes.concat(fFixes);
}
var needsFix = { "object": oFixes, "function": fFixes };
return function (object, callback) {
if (typeof callback != "function") {
throw new TypeError("callback is not a function");
}
// Normal loop, should expose all enumerable properties
// in conforming browsers
for (var prop in object) {
if (tddjs.isOwnProperty(object, prop)) {
callback(prop, object[prop]);
}
}
// Loop additional properties in non-conforming browsers
var fixes = needsFix[typeof object];
if (fixes) {
var property;
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
130 Objects and Prototypal Inheritance
for (var i = 0, l = fixes.length; i < l; i++) {
property = fixes[i];
if (tddjs.isOwnProperty(object, property)) {
callback(property, object[property]);
}
}
}
};
}());
If we change the for-in loops in the tests in Listing 7.11 to use the new
tddjs.each method, the tests will run, even on Internet Explorer. Addition-
ally, the method smoothes over a similar bug in Chrome in which function objects
prototype property is not enumerable when shadowed.
7.2 Creating Objects with Constructors
JavaScript functions have the ability to act as constructors when invoked with the
new operator, i.e., new MyConstructor(). There is nothing that differentiates
the definition of a regular function and one that constructs objects. In fact, JavaScript
provides every function with a prototype object in case it is used with the new
operator. When the function is used as a constructor to create new objects, their
internal [[Prototype]] property will be a reference to this object.
In the absence of language level checks on functions vs. constructors, con-
structor names are usually capitalized to indicate their intended use. Regardless of
whether you use constructors or not in your own code, you should honor this idiom
by not capitalizing names of functions and objects that are not constructors.
7.2.1 prototype and [[Prototype]]
The word “prototype” is used to describe two concepts. First, a constructor has
a public prototype property. When the constructor is used to create new ob-
jects, those objects will have an internal [[Prototype]] property that is a refer-
ence to the constructor’s prototype property. Second, the constructor has an
internal [[Prototype]] that references the prototype of the constructor that cre-
ated it, most commonly Function.prototype. All JavaScript objects have
an internal [[Prototype]] property; only function objects have the prototype
property.
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
7.2 Creating Objects with Constructors 131
7.2.2 Creating Objects with new
When a function is called with the new operator, a new JavaScript object is created.
The function is then called using the newly created object as the this value along
with any arguments that were passed in the original call.
In Listing 7.13 we see how creating objects with constructors compares with
the object literal we’ve been using so far.
Listing 7.13 Creating objects with constructors
function Circle(radius) {
this.radius = radius;
}
// Create a circle
var circ = new Circle(6);
// Create a circle-like object
var circ2 = { radius: 6 };
The two objects share the same properties—the radius property along with
properties inherited from Object.prototype. Although both objects inherit
from Object.prototype, circ2 does so directly (i.e., its [[Prototype]] prop-
erty is a reference to Object.prototype), whereas circ does so indirectly
through Circle.prototype. We can use the instanceof operator to deter-
mine the relationship between objects. Additionally, we can use the constructor
property to inspect their origin, as seen in Listing 7.14.
Listing 7.14 Inspecting objects
TestCase("CircleTest", {
"test inspect objects": function () {
var circ = new Circle(6);
var circ2 = { radius: 6 };
assertTrue(circ instanceof Object);
assertTrue(circ instanceof Circle);
assertTrue(circ2 instanceof Object);
assertEquals(Circle, circ.constructor);
assertEquals(Object, circ2.constructor);
}
});
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.
ptg
132 Objects and Prototypal Inheritance
The expression a instanceof b will return true whenever the internal
[[Prototype]] property of a, or one of the objects on its prototype chain, is the
same object as b.prototype.
7.2.3 Constructor Prototypes
Functions are always assigned a prototype property, which will be set as the in-
ternal [[Prototype]] property of objects created by the function when used as a con-
structor. The assigned prototype object’s prototype is in turnObject.prototype
and it defines a single property, constructor, which is a reference to the con-
structor itself. Because the new operator may be used with any expression that
results in a constructor, we can use this property to dynamically create new objects
of the same type as a known object. In Listing 7.15 we use the constructor
property of a circle object to create a new circle object.
Listing 7.15 Creating objects of the same kind
"test should create another object of same kind":
function () {
var circle = new Circle(6);
var circle2 = new circle.constructor(9);
assertEquals(circle.constructor, circle2.constructor);
assertTrue(circle2 instanceof Circle);
}
7.2.3.1 Adding Properties to the Prototype
We can give our new circle objects new functionality by augmenting the proto-
type property of the constructor, much like we extended the behavior of native
objects in Section 7.1.3, Extending Objects through the Prototype Chain. Listing 7.16
adds three methods for circle objects to inherit.
Listing 7.16 Adding properties to Circle.prototype
Circle.prototype.diameter = function () {
return this.radius * 2;
};
Circle.prototype.circumference = function () {
return this.diameter() * Math.PI;
};
From the Library of WoweBook.Com
Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark.