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

Test Driven JavaScript Development- P9 pdf

20 327 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 191,12 KB

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

Nội dung

Listing 7.50 Borrowing from Array.prototype "test arguments should borrow from Array.prototype": function { function addToArray { var args = Array.prototype.slice.callarguments; var arr

Trang 1

accept an optional properties argument We will discuss this method further in

Chapter 8, ECMAScript 5th Edition.

7.5.2 The tddjs.extend Method

Often we want to borrow behavior from one or more other objects to build the functionality we’re after We’ve seen this a couple of times already Remember the argumentsobject? It acts roughly like an array, but it is not a true array, and

as such, lacks certain properties we might be interested in The arguments ob-ject does, however, possess the most important aspects of an array: the length property, and numerical indexes as property names These two aspects are enough for most methods on Array.prototype to consider arguments an object that “walks like a duck, swims like a duck, and quacks like a duck” and there-fore is a duck (or rather, an array) This means that we can borrow methods from Array.prototype by calling them with arguments as this, as seen in Listing 7.50

Listing 7.50 Borrowing from Array.prototype

"test arguments should borrow from Array.prototype":

function () { function addToArray() { var args = Array.prototype.slice.call(arguments);

var arr = args.shift();

return arr.concat(args);

} var result = addToArray([], 1, 2, 3);

assertEquals([1, 2, 3], result);

} The example borrows the slice function and calls it on the arguments object Because we don’t give it any other arguments, it will return the whole array, but the trick is now we’ve effectively converted arguments to an array, on which

we can call the usual array methods

Remember in Chapter 5, Functions, we illustrated implicit binding of this

by copying a function from one object to another Doing so causes both objects

to share the same function object, so it’s a memory efficient way to share behavior

Listing 7.51 shows an example

Trang 2

Listing 7.51 Borrowing explicitly

"test arguments should borrow explicitly from Array.prototype":

function () {

function addToArray() { arguments.slice = Array.prototype.slice;

var args = arguments.slice();

var arr = args.shift();

return arr.concat(args);

} var result = addToArray([], 1, 2, 3);

assertEquals([1, 2, 3], result);

}

Using this technique, we can build objects that are collections of methods related over some topic, and then add all the properties of this object onto another object

to “bless” it with the behavior Listing 7.52 shows the initial test case for a method

that will help us do exactly that

Listing 7.52 Initial test case for tddjs.extend

TestCase("ObjectExtendTest", {

setUp: function () { this.dummy = { setName: function (name) { return (this.name = name);

}, getName: function () { return this.name || null;

} };

},

"test should copy properties": function () { var object = {};

tddjs.extend(object, this.dummy);

assertEquals("function", typeof object.getName);

assertEquals("function", typeof object.setName);

} });

Trang 3

The test sets up a dummy object in the setUp method It then asserts that when extending an object, all the properties from the source object is copied over

This method is definitely eligible for the Internet Explorer DontEnum bug, so Listing 7.53 uses the tddjs.each method to loop the properties

Listing 7.53 Initial implementation of tddjs.extend

tddjs.extend = (function () { function extend(target, source) { tddjs.each(source, function (prop, val) { target[prop] = val;

});

} return extend;

}());

The next step, seen in Listing 7.54, is to ensure that the two arguments are safe

to use Any object will do on both sides; we simply need to make sure they’re not nullor undefined

Listing 7.54 Extending null

"test should return new object when source is null":

function () { var object = tddjs.extend(null, this.dummy);

assertEquals("function", typeof object.getName);

assertEquals("function", typeof object.setName);

} Note the expected return value Listing 7.55 shows the implementation

Listing 7.55 Allowing target to be null

function extend(target, source) { target = target || {};

tddjs.each(source, function (prop, val) { target[prop] = val;

});

return target;

}

Trang 4

If the source is not passed in, we can simply return the target untouched, as seen in Listing 7.56

Listing 7.56 Dealing with only one argument

"test should return target untouched when no source":

function () {

var object = tddjs.extend({});

var properties = [];

for (var prop in object) {

if (tddjs.isOwnProperty(object, prop)) { properties.push(prop);

} } assertEquals(0, properties.length);

}

Now something interesting happens This test passes in most browsers, even when source is undefined This is because of browsers’ forgiving nature, but

it is violating ECMAScript 3, which states that a TypeError should be thrown

when a for-in loop is trying to loop null or undefined Interestingly, Internet

Explorer 6 is one of the browsers that does behave as expected here ECMAScript

5 changes this behavior to not throw when the object being looped is null or

undefined Listing 7.57 shows the required fix

Listing 7.57 Aborting if there is no source

function extend(target, source) {

target = target || {};

if (!source) { return target;

} /* */

}

Note that tddjs.extend always overwrites if target already defines a given property We could embellish this method in several ways—adding a boolean

option to allow/prevent overwrite, adding an option to allow/prevent shadowing

of properties on the prototype chain, and so on Your imagination is your limit

Trang 5

7.5.3 Mixins

An object that defines a set of properties that can be used with the tddjs.extend

method to “bless” other objects is often called a mixin For instance, the Ruby

standard library defines a bunch of useful methods in its Enumerable module,

which may be mixed in to any object that supports the each method Mixins provide

an incredibly powerful mechanism for sharing behavior between objects We could easily port the enumerable module from Ruby to a JavaScript object and mix it in with, e.g., Array.protoype to give all arrays additional behavior (remember to not loop arrays with for-in) Listing 7.58 shows an example that assumes that the enumerableobject contains at least a reject method

Listing 7.58 Mixing in the enumerable object to Array.prototype

TestCase("EnumerableTest", {

"test should add enumerable methods to arrays":

function () { tddjs.extend(Array.prototype, enumerable);

var even = [1, 2, 3, 4].reject(function (i) { return i % 2 == 1;

});

assertEquals([2, 4], even);

} });

Assuming we are in a browser that supports Array.prototype.forEach,

we could implement the reject method as seen in Listing 7.59

Listing 7.59 Excerpt of JavaScript implementation of Ruby’s enumerable

var enumerable = { /* */

reject: function (callback) { var result = [];

this.forEach(function (item) {

if (!callback(item)) { result.push(item);

} });

Trang 6

return result;

} };

7.6 Summary

In this chapter we’ve seen several approaches to JavaScript object creation, and

sharing of behavior between them We started by gaining a thorough understanding

of how JavaScript properties and the prototype chain work We then moved on

to constructors and used them in conjunction with their prototype property

to implement an emulation of classical inheritance Pseudo-classical inheritance

can be tempting for developers unfamiliar with prototypes and JavaScript’s native

inheritance mechanism, but can lead to complex solutions that are computationally

inefficient

Dispensing the constructors, we moved on to prototype-based inheritance and

explored how JavaScript allows us to work solely on objects by extending

ob-jects with other obob-jects By implementing a simple Object.create function, we

avoided some of the confusion introduced by constructors and were able to see

clearer how the prototype chain helps us extend the behavior of our objects

Functional inheritance showed us how closures can be used to store state and

achieve truly private members and methods

To wrap it all up, we looked at object composition and mixins in JavaScript, combining all of the previously discussed patterns Mixins are a great match for

JavaScript, and often offer a great way to share behavior between objects

Which technique to use? The answer will vary depending on whom you ask

There is no one right answer, because it depends on your situation As we’ve seen,

there are trade-offs when choosing between a pseudo-classical approach and a

func-tional approach that will be affected by whether object creation, method invocation,

memory usage, or security is the most crucial aspect of your application

Through-out this book we’ll see how to use a few of the techniques presented in this chapter

in real life examples

Trang 7

8

ECMAScript 5th Edition

In December 2009, ECMA-262 5th Edition, commonly referred to as ECMAScript 5, or simply ES5, was finalized and published by ECMA International

This marked the first significant update to the core JavaScript language in 10 years

ECMAScript 5 is the successor to ECMAScript 3, and is a mostly backwards com-patible update of the language that codifies innovation by browser vendors over the past decade and introduces a few new features

ECMAScript 4 was never realized, and is part of the answer to why the language could go without standardized updates for 10 years This draft was widely considered too revolutionary an update, and introduced several features that would not work well with existing browsers To this day, Adobe’s ActionScript (used in Flash) and Microsoft’s JScript.Net are the only runtimes to implement a significant amount of the proposed updates from ES4

In this chapter we will take a cursory look at the most interesting changes in ES5, and have a look at some of the programming patterns the new specification enables Particularly interesting are new additions to objects and properties, and these will be afforded the bulk of our attention Note that this chapter does not

cover all changes and additions in ECMAScript 5.

8.1 The Close Future of JavaScript

Backwards compatibility has been a major concern of ES5 JavaScript is ubiquitous—every web browser released since the mid to late nineties supports

Trang 8

it in some form or other; it’s on phones and other mobile devices; it’s used to

de-velop extensions for browsers such as Mozilla Firefox and Google Chrome and has

even taken the front seat in Gnome Shell, the defining technology in the Gnome 3

desktop environment for Linux JavaScript runtimes are wild beasts When

deploy-ing a script on the web, we can never know what kind of runtime will attempt to

run our code Couple this with the huge amount of JavaScript already deployed on

the web, and you will have no problem imagining why backwards compatibility has

been a key concern for ES5 The goal is not to “break the web,” but rather bring it

forward

ES5 has worked hard to standardize, or codify, existing de facto standards—

innovation in the wild adopted across browser vendors as well as common use

cases found in modern JavaScript libraries String.prototype.trim and

Function.prototype.bindare good examples of the latter, whereas attribute

getters and setters are good examples of the former

Additionally, ES5 introduces strict mode, which points out the way moving

forward Strict mode can be enabled with a simple string literal, and makes ES5

compliant implementations, well, stricter in their parsing and execution of scripts

Strict mode sheds some of JavaScript’s bad parts and is intended to serve as the

starting point for future updates to the language

The reason this section is entitled the close future of JavaScript is that there

is reason to believe that we won’t have to wait another 10 years for good browser

support This is of course speculation on my (and others) part, but as ES5

cod-ifies some de facto standards, some of these features are already available in

a good selection of browsers today Additionally, the last couple of years have

seen a somewhat revitalized “browser war,” in which vendors compete harder

than in a long time in creating modern standards compliant and performant

browsers

Microsoft and their Internet Explorer browser have slowed down web devel-opers for many years, but recent development seems to suggest that they’re at least

back in the game trying to stay current Besides, browser usage looks vastly different

today compared with only 5 years ago, and fair competition regulations are already

forcing Windows users in Europe to make a conscious choice of browser

All in all, I am fairly positive to the adoption of ES5 Some of it is already supported in browsers like Chrome, Firefox, and Safari, and preview releases of all

the aforementioned browsers adds more At the time of writing, even previews of

Internet Explorer 9 already implement most of ES5 I expect the situation to look

even brighter once this book hits the shelves

Trang 9

8.2 Updates to the Object Model

Of all the updates in ECMAScript 5, I find the updated object model to be the

most exciting As we discussed in Chapter 7, Objects and Prototypal Inheritance,

JavaScript objects are simple mutable collections of properties, and even though ES3 defines attributes to control whether properties can be overwritten, deleted, and enumerated, they remain strictly internal, and thus cannot be harnessed by client objects This means that objects that are dependent on their (public and mutable) properties need to employ more error checking than desired to remain reasonably robust

8.2.1 Property Attributes

ES5 allows user-defined property descriptors to overwrite any of the following attributes for a given property

• enumerable — Internal name [[Enumerable]], formerly [[DontEnum]], controls whether the property is enumerated in for-in loops

• configurable — Internal name [[Configurable]], formerly [[DontDelete]], controls whether the property can be deleted with delete

• writable — Internal name [[Writable]], formerly [[ReadOnly]], controls whether the property can be overwritten

• get — Internal name [[Get]], a function that computes the return value of property access

• set — Internal name [[Set]], a function that is called with the assigned value when the property is assigned to

In ES5 we can set a property in two ways The old school way, shown in Listing 8.1, in which we simply assign a value to a property, or the new way, shown

in Listing 8.2

Listing 8.1 Simple name/value assignment

var circle = {};

circle.radius = 4;

Trang 10

Listing 8.2 Empowered ES5 properties

TestCase("ES5ObjectTest", {

"test defineProperty": function () { var circle = {};

Object.defineProperty(circle, "radius", { value: 4,

writable: false, configurable: false });

assertEquals(4, circle.radius);

} });

The Object.defineProperty method can be used not only to define new properties on an object, but also to update the descriptor of a property Updating

a property descriptor only works if the property’s configurable attribute is set

to true Listing 8.3 shows an example of how you can use the existing descriptor

to update only some attributes

Listing 8.3 Changing a property descriptor

"test changing a property descriptor": function () {

var circle = { radius: 3 };

var descriptor = Object.getOwnPropertyDescriptor(circle, "radius");

descriptor.configurable = false;

Object.defineProperty(circle, "radius", descriptor);

delete circle.radius;

// Non-configurable radius cannot be deleted assertEquals(3, circle.radius);

}

In addition to controlling the property attributes, ES5 also allows trol over the internal [[Extensible]] property of an object This property

con-trols whether or not properties can be added to the object Calling Object

preventExtensions(obj)shuts the object down for further extension and

cannot be undone

Preventing object extensions and setting property attributes writable and configurableto false means you can now create immutable objects This

removes a lot of error checking and complexity brought on by the fact that

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

TỪ KHÓA LIÊN QUAN