1. Trang chủ
  2. » Công Nghệ Thông Tin

Test Driven JavaScript Development- P7 doc

20 262 0
Tài liệu đã được kiểm tra trùng lặp

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 20
Dung lượng 202,07 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

Besides, serializing object arguments using tddjs.uid, although simple and fast, would cause the method to possibly assign new properties to arguments.. In the next chapter we will take

Trang 1

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];

};

};

}

Trang 2

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 memoize method 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

Trang 3

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 of memoize 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

Trang 4

ptg

Trang 5

7

Objects and Prototypal

Inheritance

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"

Trang 6

}, 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

Trang 7

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 inheriinheri-tance, in

1 Host objects will be discussed in Chapter 10, Feature Detection.

Trang 8

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

Trang 9

// 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

Trang 10

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

Trang 11

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)

Ngày đăng: 03/07/2014, 05:20

TỪ KHÓA LIÊN QUAN

w