Test Driven JavaScript Development- P8

Chia sẻ: Cong Thanh | Ngày: | Loại File: PDF | Số trang:20

lượt xem

Test Driven JavaScript Development- P8

Mô tả tài liệu
  Download Vui lòng tải xuống để xem tài liệu đầy đủ

Nội dung Text: Test Driven JavaScript Development- P8

  1. 7.2 Creating Objects with Constructors 133 Circle.prototype.area = function () { return this.radius * this.radius * Math.PI; }; Listing 7.17 shows a simple test to verify that objects do indeed inherit the methods. Listing 7.17 Testing Circle.prototype.diameter "test should inherit properties from Circle.prototype": function () { var circle = new Circle(6); assertEquals(12, circle.diameter()); } Repeating Circle.prototype quickly becomes cumbersome and expensive (in terms of bytes to go over the wire) when adding more than a few properties to the prototype. We can improve this pattern in a number of ways. Listing 7.18 shows the shortest way—simply provide an object literal as the new prototype. Listing 7.18 Assigning Circle.prototype Circle.prototype = { diameter: function () { return this.radius * 2; }, circumference: function () { return this.diameter() * Math.PI; }, area: function () { return this.radius * this.radius * Math.PI; } }; Unfortunately, this breaks some of our previous tests. In particular, the assertion in Listing 7.19 no longer holds. Listing 7.19 Failing assertion on constructor equality assertEquals(Circle, circle.constructor) Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  2. 134 Objects and Prototypal Inheritance When we assign a new object to Circle.prototype, JavaScript no longer creates a constructor property for us. This means that the [[Get]] for con- structor will go up the prototype chain until a value is found. In the case of our constructor, the result is Object.prototype whose constructor property is Object, as seen in Listing 7.20. Listing 7.20 Broken constructor property "test constructor is Object when prototype is overridden": function () { function Circle() {} Circle.prototype = {}; assertEquals(Object, new Circle().constructor); } Listing 7.21 solves the problem by assigning the constructor property manually. Listing 7.21 Fixing the missing constructor property Circle.prototype = { constructor: Circle, // ... }; To avoid the problem entirely, we could also extend the given prototype prop- erty in a closure to avoid repeating Circle.prototype for each property. This approach is shown in Listing 7.22. Listing 7.22 Avoiding the missing constructor problem (function (p) { p.diameter = function () { return this.radius * 2; }; p.circumference = function () { return this.diameter() * Math.PI; }; p.area = function () { return this.radius * this.radius * Math.PI; }; }(Circle.prototype)); Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  3. 7.2 Creating Objects with Constructors 135 By not overwriting the prototype property, we are also avoiding its constructor property being enumerable. The object provided for us has the DontEnum attribute set, which is impossible to recreate when we assign a cus- tom object to the prototype property and manually restore the constructor property. 7.2.4 The Problem with Constructors There is a potential problem with constructors. Because there is nothing that sep- arates a constructor from a function, there is no guarantee that someone won’t use your constructor as a function. Listing 7.23 shows how a missing new keyword can have grave effects. Listing 7.23 Constructor misuse "test calling prototype without 'new' returns undefined": function () { var circle = Circle(6); assertEquals("undefined", typeof circle); // Oops! Defined property on global object assertEquals(6, radius); } This example shows two rather severe consequences of calling the constructor as a function. Because the constructor does not have a return statement, the type of the circle ends up being undefined. Even worse, because we did not call the function with the new operator, JavaScript did not create a new object and set it as the function’s this value for us. Thus, the function executes on the global object, causing this.radius = radius to set a radius property on the global object, as shown by the second assertion in Listing 7.23. In Listing 7.24 the problem is mitigated by use of the instanceof operator. Listing 7.24 Detecting constructor misuse function Circle(radius) { if (!(this instanceof Circle)) { return new Circle(radius); } this.radius = radius; } Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  4. 136 Objects and Prototypal Inheritance Whenever someone forgets the new operator when calling the constructor, this will refer to the global object rather than a newly created object. By using the instanceof operator, we’re able to catch this, and can explicitly call the constructor over again with the same arguments and return the new object. ECMA-262 defines the behavior for all the native constructors when used as functions. The result of calling a constructor as a function often has the same effect as our implementation above—a new object is created as if the function was actually called with the new operator. Assuming that calling a constructor without new is usually a typo, you may want to discourage using constructors as functions. Throwing an error rather than sweeping the error under the rug will probably ensure a more consistent code base in the long run. 7.3 Pseudo-classical Inheritance Equipped with our understanding of constructors and their prototype properties, we can now create arbitrary hierarchies of objects, in much the same way one would create class hierarchies in a classical language. We will do this with Sphere, a constructor whose prototype property inherits from Circle.prototype rather than Object.prototype. We need Sphere.prototype to refer to an object whose internal [[Pro- totype]] is Circle.prototype. In other words, we need a circle object to set up this link. Unfortunately, this process is not straightforward; In order to create a circle object we need to invoke the Circle constructor. However, the constructor may provide our prototype object with unwanted state, and it may even fail in the absence of input arguments. To circumvent this potential problem, Listing 7.25 uses an intermediate constructor that borrows Circle.prototype. Listing 7.25 Deeper inheritance function Sphere(radius) { this.radius = radius; } Sphere.prototype = (function () { function F() {}; F.prototype = Circle.prototype; return new F(); }()); Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  5. 7.3 Pseudo-classical Inheritance 137 // Don't forget the constructor - else it will resolve as // Circle through the prototype chain Sphere.prototype.constructor = Sphere; Now we can create spheres that inherit from circles, as shown by the test in Listing 7.26. Listing 7.26 Testing the new Sphere constructor "test spheres are circles in 3D": function () { var radius = 6; var sphere = new Sphere(radius); assertTrue(sphere instanceof Sphere); assertTrue(sphere instanceof Circle); assertTrue(sphere instanceof Object); assertEquals(12, sphere.diameter()); } 7.3.1 The Inherit Function In Listing 7.25 we extended the Sphere constructor with the Circle construc- tor by linking their prototypes together, causing sphere objects to inherit from Circle.prototype. The solution is fairly obscure, especially when compared with inheritance in other languages. Unfortunately, JavaScript does not offer any abstractions over this concept, but we are free to implement our own. Listing 7.27 shows a test for what such an abstraction might look like. Listing 7.27 Specification for inherit TestCase("FunctionInheritTest", { "test should link prototypes": function () { var SubFn = function () {}; var SuperFn = function () {}; SubFn.inherit(SuperFn); assertTrue(new SubFn() instanceof SuperFn); } }); We already implemented this feature in Listing 7.25, so we only need to move it into a separate function. Listing 7.28 shows the extracted function. Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  6. 138 Objects and Prototypal Inheritance Listing 7.28 Implementing inherit if (!Function.prototype.inherit) { (function () { function F() {} Function.prototype.inherit = function (superFn) { F.prototype = superFn.prototype; this.prototype = new F(); this.prototype.constructor = this; }; }()); } This implementation uses the same intermediate constructor for all calls, only assigning the prototype for each call. Using this new function we can clean up our circles and spheres, as seen in Listing 7.29. Listing 7.29 Making Sphere inherit from Circle with inherit function Sphere (radius) { this.radius = radius; } Sphere.inherit(Circle); More or less all the major JavaScript libraries ship with a variant of the inherit function, usually under the name extend. I’ve named it inherit in order to avoid confusion when we turn our attention to another extend method later in this chapter. 7.3.2 Accessing [[Prototype]] The inherit function we just wrote makes it possible to easily create object hierarchies using constructors. Still, comparing the Circle and Sphere constructors tells us something isn’t quite right—they both perform the same initialization of the radius property. The inheritance we’ve set up exists on the object level through the prototype chain, the constructors are not linked in the same way a class and a subclass are linked in a classical language. In particular, JavaScript has no super to directly refer to properties on objects from which an object inherits. In fact, ECMA-262 3rd edition provides no way at all to access the internal [[Prototype]] property of an object. Even though there is no standardized way of accessing the [[Prototype]] of an object, some implementations provide a non-standard __proto__ property Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  7. 7.3 Pseudo-classical Inheritance 139 that exposes the internal [[Prototype]] property. ECMAScript 5 (ES5) has standardized this feature as the new method Object.getPrototypeOf (object), giving you the ability to look up an object’s [[Prototype]]. In browsers in which __proto__ is not available, we can sometimes use the constructor property to get the [[Prototype]], but it requires that the object was in fact created using a constructor and that the constructor property is set correctly. 7.3.3 Implementing super So, JavaScript has no super, and it is not possible to traverse the prototype chain in a standardized manner that is guaranteed to work reliably cross-browser. It’s still possible to emulate the concept of super in JavaScript. Listing 7.30 achieves this by calling the Circle constructor from within the Sphere constructor, passing the newly created object as the this value. Listing 7.30 Accessing the Circle constructor from within the Sphere constructor function Sphere(radius) { Circle.call(this, radius); } Sphere.inherit(Circle); Running the tests confirms that sphere objects still work as intended. We can employ the same technique to access “super methods” from other methods as well. In Listing 7.31 we call the area method on the prototype. Listing 7.31 Calling a method on the prototype chain Sphere.prototype.area = function () { return 4 * Circle.prototype.area.call(this); }; Listing 7.32 shows a simple test of the new method. Listing 7.32 Calculating surface area "test should calculate sphere area": function () { var sphere = new Sphere(3); assertEquals(113, Math.round(sphere.area())); } Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  8. 140 Objects and Prototypal Inheritance The drawback of this solution is its verbosity; The call to Circle. prototype.area is long and couples Sphere very tightly to Circle. To miti- gate this, Listing 7.33 makes the inherit function set up a “super” link for us. Listing 7.33 Expecting the _super link to refer to the prototype "test should set up link to super": function () { var SubFn = function () {}; var SuperFn = function () {}; SubFn.inherit(SuperFn); assertEquals(SuperFn.prototype, SubFn.prototype._super); } Note the leading underscore. ECMA-262 defines super as a reserved word intended for future use, so we best not use it. The implementation in Listing 7.34 is still straightforward. Listing 7.34 Implementing a link to the prototype if (!Function.prototype.inherit) { (function () { function F() {} Function.prototype.inherit = function (superFn) { F.prototype = superFn.prototype; this.prototype = new F(); this.prototype.constructor = this; this.prototype._super = superFn.prototype; }; }()); } Using this new property, Listing 7.35 simplifies Sphere.prototype.area. Listing 7.35 Calling a method on the prototype chain Sphere.prototype.area = function () { return 4 * this._super.area.call(this); }; The _super Method Although I would definitely not recommend it, someone serious about emulating classical inheritance in JavaScript would probably prefer _super to be a method Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  9. 7.3 Pseudo-classical Inheritance 141 rather than a simple link to the prototype. Calling the method should magically call the corresponding method on the prototype chain. The concept is illustrated in Listing 7.36. Listing 7.36 Testing the _super method "test super should call method of same name on protoype": function () { function Person(name) { this.name = name; } Person.prototype = { constructor: Person, getName: function () { return this.name; }, speak: function () { return "Hello"; } }; function LoudPerson(name) { Person.call(this, name); } LoudPerson.inherit2(Person, { getName: function () { return this._super().toUpperCase(); }, speak: function () { return this._super() + "!!!"; } }); var np = new LoudPerson("Chris"); assertEquals("CHRIS", np.getName()); assertEquals("Hello!!!", np.speak()); } In this example we are using Function.prototype.inherit2 to estab- lish the prototype chain for the LoudPerson objects. It accepts a second argument, Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  10. 142 Objects and Prototypal Inheritance which is an object that defines the methods on LoudPerson.prototype that need to call _super. Listing 7.37 shows one possible implementation. Listing 7.37 Implementing _super as a method if (!Function.prototype.inherit2) { (function () { function F() {} Function.prototype.inherit2 = function (superFn, methods) { F.prototype = superFn.prototype; this.prototype = new F(); this.prototype.constructor = this; var subProto = this.prototype; tddjs.each(methods, function (name, method) { // Wrap the original method subProto[name] = function () { var returnValue; var oldSuper = this._super; this._super = superFn.prototype[name]; try { returnValue = method.apply(this, arguments); } finally { this._super = oldSuper; } return returnValue; }; }); }; }()); } This implementation allows for calls to this._super() as if the method had special meaning. In reality, we’re wrapping the original methods in a new function that takes care of setting this._super to the right method before calling the original method. Using the new inherit function we could now implement Sphere as seen in Listing 7.38. Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  11. 7.3 Pseudo-classical Inheritance 143 Listing 7.38 Implementing Sphere with inherit2 function Sphere(radius) { Circle.call(this, radius); } Sphere.inherit2(Circle, { area: function () { return 4 * this._super(); } }); Performance of the super Method Using the inherit2 method we can create constructors and objects that come pretty close to emulating classical inheritance. It does not, however, per- form particularly well. By redefining all the methods and wrapping them in closures, inherit2 will not only be slower than inherit when extend- ing constructors, but calling this._super() will be slower than calling this._super.method.call(this) as well. Further hits to performance are gained by the try-catch, which is used to ensure that this._super is restored after the method has executed. As if that wasn’t enough, the method approach only allows static inheritance. Adding new methods to Circle.prototype will not automatically expose _super in same named methods on Sphere.prototype. To get that working we would have to imple- ment some kind of helper function to add methods that would add the enclosing function that sets up _super. In any case, the result would be less than elegant and would introduce a possibly significant performance overhead. I hope you never use this function; JavaScript has better patterns in store. If anything, I think the _super implementation is a testament to JavaScript’s flexi- bility. JavaScript doesn’t have classes, but it gives you the tools you need to build them, should you need to do so. A _super Helper Function A somewhat saner implementation, although not as concise, can be achieved by implementing _super as a helper function piggybacking the prototype link, as seen in Listing 7.39. Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  12. 144 Objects and Prototypal Inheritance Listing 7.39 A simpler _super implementation function _super(object, methodName) { var method = object._super && object._super[methodName]; if (typeof method != "function") { return; } // Remove the first two arguments (object and method) var args = Array.prototype.slice.call(arguments, 2); // Pass the rest of the arguments along to the super return method.apply(object, args); } Listing 7.40 shows an example of using the _super function. Listing 7.40 Using the simpler _super helper function LoudPerson(name) { _super(this, "constructor", name); } LoudPerson.inherit(Person); LoudPerson.prototype.getName = function () { return _super(this, "getName").toUpperCase(); }; LoudPerson.prototype.say = function (words) { return _super(this, "speak", words) + "!!!"; }; var np = new LoudPerson("Chris"); assertEquals("CHRIS", np.getName()); assertEquals("Hello!!!", np.say("Hello")); This is unlikely to be faster to call than spelling out the method to call directly, but at least it defeats the worst performance issue brought on by implementing _super as a method. In general, we can implement sophisticated object oriented solutions without the use of the _super crutch, as we will see both throughout this chapter and the sample projects in Part III, Real-World Test-Driven Development in JavaScript. Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  13. 7.4 Encapsulation and Information Hiding 145 7.4 Encapsulation and Information Hiding JavaScript does not have access modifiers such as public, protected, and private. Additionally, the property attributes DontDelete and ReadOnly are unavailable to us. As a consequence, the objects we’ve created so far consist solely of public properties. In addition to being public, the objects and properties are also mutable in any context because we are unable to freeze them. This means our object’s internals are open and available for modification by anyone, possibly compromising the security and integrity of our objects. When using constructors and their prototypes, it is common to pre- fix properties with an underscore if they are intended to be private, i.e., this._privateProperty. Granted, this does not protect the properties in any way, but it may help users of your code understand which properties to stay away from. We can improve the situation by turning to closures, which are capable of producing a scope for which there is no public access. 7.4.1 Private Methods By using closures, we can create private methods. Actually, they’re more like pri- vate functions, as attaching them to an object effectively makes them public. These functions will be available to other functions defined in the same scope, but they will not be available to methods added to the object or its prototype at a later stage. Listing 7.41 shows an example. Listing 7.41 Defining a private function function Circle(radius) { this.radius = radius; } (function (circleProto) { // Functions declared in this scope are private, and only // available to other functions declared in the same scope function ensureValidRadius(radius) { return radius >= 0; } function getRadius() { return this.radius; } function setRadius(radius) { if (ensureValidRadius(radius)) { Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  14. 146 Objects and Prototypal Inheritance this.radius = radius; } } // Assigning the functions to properties of the prototype // makes them public methods circleProto.getRadius = getRadius; circleProto.setRadius = setRadius; }(Circle.prototype)); In Listing 7.41 we create an anonymous closure that is immediately executed with Circle.prototype as the only argument. Inside we add two public meth- ods, and keep a reference to one private function, ensureValidRadius. If we need a private function to operate on the object, we can either design it to accept a circle object as first argument, or invoke it with privFunc.call(this, /* args... */), thus being able to refer to the circle as this inside the function. We could also have used the existing constructor as the enclosing scope to hold the private function. In that case we need to also define the public methods inside it, so they share scope with the private function, as seen in Listing 7.42. Listing 7.42 Using a private function inside the constructor function Circle(radius) { this.radius = radius; function ensureValidRadius(radius) { return radius >= 0; } function getRadius() { return this.radius; } function setRadius(radius) { if (ensureValidRadius(radius)) { this.radius = radius; } } // Expose public methods this.getRadius = getRadius; this.setRadius = setRadius; } Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  15. 7.4 Encapsulation and Information Hiding 147 This approach has a serious drawback in that it creates three function objects for every person object created. The original approach using a closure when adding properties to the prototype will only ever create three function objects that are shared between all circle objects. This means that creating n circle objects will cause the latter version to use approximately n times as much memory as the original suggestion. Additionally, object creation will be significantly slower because the constructor has to create the function objects as well. On the other hand, property resolution is quicker in the latter case because the properties are found directly on the object and no access to the prototype chain is needed. In deep inheritance structures, looking up methods through the prototype chain can impact method call performance. However, in most cases inheritance structures are shallow, in which case object creation and memory consumption should be prioritized. The latter approach also breaks our current inheritance implementation. When the Sphere constructor invokes the Circle constructor, it copies over the circle methods to the newly created sphere object, effectively shadowing the methods on Sphere.prototype. This means that the Sphere constructor needs to change as well if this is our preferred style. 7.4.2 Private Members and Privileged Methods In the same way private functions may be created inside the constructor, we can create private members here, allowing us to protect the state of our objects. In order to make anything useful with this we’ll need some public methods that can access the private properties. Methods created in the same scope—meaning the constructor—will have access to the private members, and are usually referred to as “privileged methods.” Continuing our example, Listing 7.43 makes radius a private member of Circle objects. Listing 7.43 Using private members and privileged methods function Circle(radius) { function getSetRadius() { if (arguments.length > 0) { if (arguments[0] < 0) { throw new TypeError("Radius should be >= 0"); } radius = arguments[0]; } Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  16. 148 Objects and Prototypal Inheritance return radius; } function diameter() { return radius * 2; } function circumference() { return diameter() * Math.PI; } // Expose privileged methods this.radius = getSetRadius; this.diameter = diameter; this.circumference = circumference; this.radius(radius); } The new object no longer has a numeric radius property. Instead, it stores its state in a local variable. This means that none of the nested functions needs this anymore, so we can simplify the calls to them. Objects created with this constructor will be robust, because outside code cannot tamper with its internal state except through the public API. 7.4.3 Functional Inheritance In his book, JavaScript: The Good Parts [5], and on his website, Douglas Crockford promotes what he calls functional inheritance. Functional inheritance is the next logical step from Listing 7.43, in which we’ve already eliminated most uses of the this keyword. In functional inheritance, the use of this is eliminated completely and the constructor is no longer needed. Instead, the constructor becomes a reg- ular function that creates an object and returns it. The methods are defined as nested functions, which can access the free variables containing the object’s state. Listing 7.44 shows an example. Listing 7.44 Implementing circle using functional inheritance function circle(radius) { // Function definitions as before return { radius: getSetRadius, Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  17. 7.4 Encapsulation and Information Hiding 149 diameter: diameter, area: area, circumference: circumference }; } Because circle is no longer a constructor, its name is no longer capitalized. To use this new function we omit the new keyword as seen in Listing 7.45. Listing 7.45 Using the functional inheritance pattern "test should create circle object with function": function () { var circ = circle(6); assertEquals(6, circ.radius()); circ.radius(12); assertEquals(12, circ.radius()); assertEquals(24, circ.diameter()); } Crockford calls an object like the ones created by circle durable [6]. When an object is durable, its state is properly encapsulated, and it cannot be compromised by outside tampering. This is achieved by keeping state in free variables inside closures, and by never referring to the object’s public interface from inside the object. Recall how we defined all the functions as inner private functions first, and then assigned them to properties? By always referring to the object’s capability in terms of these inner functions, offending code cannot compromise our object by, e.g., injecting its own methods in place of our public methods. Extending Objects How can we achieve inheritance using this model? Rather than returning a new object with the public properties, we create the object we want to extend, add methods to it, and return it. You might recognize this design as the decorator pat- tern, and it is. The object we want to extend can be created in any way—through a constructor, from another object producing function, or even from the argu- ments provided to the function. Listing 7.46 shows an example using spheres and circles. Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  18. 150 Objects and Prototypal Inheritance Listing 7.46 Implementing sphere using functional inheritance function sphere(radius) { var sphereObj = circle(radius); var circleArea = sphereObj.area; function area() { return 4 * circleArea.call(this); } sphereObj.area = area; return sphereObj; } The inheriting function may of course provide private variables and functions of its own. It cannot, however, access the private variables and functions of the object it builds upon. The functional style is an interesting alternative to the pseudo-classical con- structors, but comes with its own limitations. The two most obvious drawbacks of this style of coding are that every object keeps its own copy of every function, increasing memory usage, and that in practice we aren’t using the prototype chain, which means more cruft when we want to call the “super” function or something similar. 7.5 Object Composition and Mixins In classical languages, class inheritance is the primary mechanism for sharing behav- ior and reusing code. It also serves the purpose of defining types, which is important in strongly typed languages. JavaScript is not strongly typed, and such classification is not really interesting. Even though we have the previously discussed construc- tor property and the instanceof operator, we’re far more often concerned with what a given object can do. If it knows how to do the things we are interested in, we are happy. Which constructor created the object to begin with is of less interest. JavaScript’s dynamic object type allows for many alternatives to type inheritance in order to solve the case of shared behavior and code reuse. In this section we’ll discuss how we can make new objects from old ones and how we can use mixins to share behavior. Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  19. 7.5 Object Composition and Mixins 151 7.5.1 The Object.create Method In Section 7.3, Pseudo-classical Inheritance, we took a dive into JavaScript construc- tors and saw how they allow for a pseudo-classical inheritance model. Unfortunately, going too far down that road leads to complex solutions that suffer on the perfor- mance side, as evidenced by our rough implementation of super. In this section, as in the previous on functional inheritance, we’ll focus solely on objects, bypassing the constructors all together. Returning to our previous example on circles and spheres, we used constructors along with the inherit function to create a sphere object that inherited proper- ties from Circle.prototype. The Object.create function takes an object argument and returns a new object that inherits from it. No constructors involved, only objects inheriting from other objects. Listing 7.47 describes the behavior with a test. Listing 7.47 Inheriting directly from objects TestCase("ObjectCreateTest", { "test sphere should inherit from circle": function () { var circle = { radius: 6, area: function () { return this.radius * this.radius * Math.PI; } }; var sphere = Object.create(circle); sphere.area = function () { return 4 * circle.area.call(this); }; assertEquals(452, Math.round(sphere.area())); } }); Here we expect the circle and sphere objects to behave as before, only we use different means of creating them. We start out with a specific circle object. Then we use Object.create to create a new object whose [[Prototype]] refers to the old object, and we use this object as the sphere. The sphere object is then modified to fit the behavior from the constructor example. Should we want new Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
  20. 152 Objects and Prototypal Inheritance spheres, we could simply create more objects like the one we already have, as seen in Listing 7.48. Listing 7.48 Creating more sphere objects "test should create more spheres based on existing": function () { var circle = new Circle(6); var sphere = Object.create(circle); sphere.area = function () { return 4 * circle.area.call(this); }; var sphere2 = Object.create(sphere); sphere2.radius = 10; assertEquals(1257, Math.round(sphere2.area())); } The Object.create function in Listing 7.49 is simpler than the previous Function.prototype.inherit method because it only needs to create a single object whose prototype is linked to the object argument. Listing 7.49 Implementing Object.create if (!Object.create) { (function () { function F() {} Object.create = function (object) { F.prototype = object; return new F(); }; }()); } We create an intermediate constructor like before and assign the object argu- ment to its prototype property. Finally we create a new object from the intermediate constructor and return it. The new object will have an internal [[Prototype]] prop- erty that references the original object, making it inherit directly from the object argument. We could also update our Function.prototype.inherit func- tion to use this method. ES5 codifies the Object.create function, which “creates a new object with a specified prototype”. Our implementation does not conform, because it does not Please purchase PDF Split-Merge on www.verypdf.com to remove this watermark. From the Library of WoweBook.Com
Đồng bộ tài khoản