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

Test Driven JavaScript Development- P9 pdf

20 327 0

Đ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

Nội dung

ptg 7.5 Object Composition and Mixins 153 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 arguments object? 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. From the Library of WoweBook.Com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 154 Objects and Prototypal Inheritance 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); } }); From the Library of WoweBook.Com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 7.5 Object Composition and Mixins 155 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 null or 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; } From the Library of WoweBook.Com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 156 Objects and Prototypal Inheritance 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. From the Library of WoweBook.Com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 7.5 Object Composition and Mixins 157 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 enumerable object 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); } }); From the Library of WoweBook.Com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 158 Objects and Prototypal Inheritance 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 objects. 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. From the Library of WoweBook.Com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 8 ECMAScript 5th Edition I n 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 updatesfor10 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 159 From the Library of WoweBook.Com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 160 ECMAScript 5th Edition 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.bind are 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. From the Library of WoweBook.Com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 8.2 Updates to the Object Model 161 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; From the Library of WoweBook.Com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 162 ECMAScript 5th Edition 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 con- 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 configurable to false means you can now create immutable objects. This removes a lot of error checking and complexity brought on by the fact that From the Library of WoweBook.Com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. [...]... few uses of this constructor Listing 8.12 Using the hybrid Circle TestCase("CircleTest", { "test Object.create backed constructor": function () { var circle = new Circle(3); assert(circle instanceof Circle); assertEquals(6, circle.diameter); circle.radius = 6; assertEquals(12, circle.diameter); Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark From the Library of WoweBook.Com... Listing 8.7 shows how we will be doing this in the future Listing 8.7 Creating an object that inherits from another object in ES5 "test inheritance, es5 style": function () { var circle = { /* */ }; var sphere = Object.create(circle); Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark From the Library of WoweBook.Com 165 8.2 Updates to the Object Model assert(circle.isPrototypeOf(sphere));... properties writable attributes to false, thus completely locking the object down for modification Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark From the Library of WoweBook.Com 164 ECMAScript 5th Edition 8.2.2 Prototypal Inheritance ECMAScript 5 makes prototypal inheritance in JavaScript more obvious, and avoids the clumsy constructor convention In ES3, the only native way to create... Object.create and a function "test using a custom create method": function () { var circle = Object.create({}, { diameter: { get: function () { return this.radius * 2; } }, circumference: { /* */ }, area: { /* */ }, create: { value: function (radius) { var circ = Object.create(this, { radius: { value: radius } }); return circ; Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark... using Object.create function Circle(radius) { var _radius; var circle = Object.create(Circle.prototype, { radius: { configurable: false, enumerable: true, set: function (r) { Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark From the Library of WoweBook.Com 168 ECMAScript 5th Edition if (typeof r != "number" || r . purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 162 ECMAScript 5th Edition Listing 8.2 Empowered ES5 properties TestCase("ES5ObjectTest", { " ;test defineProperty":. 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. the Library of WoweBook.Com Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. ptg 7.5 Object Composition and Mixins 155 The test sets up a dummy object in the setUp method.

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

TỪ KHÓA LIÊN QUAN