Phát triển Javascript - part 18 docx

10 231 0
Phát triển Javascript - part 18 docx

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

Thông tin tài liệu

ptg 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(); } }); 7.3.3.2 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. 7.3.3.3 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. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 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. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 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)) { From the Library of WoweBook.Com Download from www.eBookTM.com ptg 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; } From the Library of WoweBook.Com Download from www.eBookTM.com ptg 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]; } From the Library of WoweBook.Com Download from www.eBookTM.com ptg 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, From the Library of WoweBook.Com Download from www.eBookTM.com ptg 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. 7.4.3.1 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. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 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. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 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 From the Library of WoweBook.Com Download from www.eBookTM.com ptg 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 From the Library of WoweBook.Com Download from www.eBookTM.com . 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,. as we will see both throughout this chapter and the sample projects in Part III, Real-World Test-Driven Development in JavaScript. From the Library of WoweBook.Com Download from www.eBookTM.com ptg 7.4. 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

Ngày đăng: 04/07/2014, 22:20

Tài liệu cùng người dùng

Tài liệu liên quan