`
wenbois2000
  • 浏览: 44651 次
  • 性别: Icon_minigender_1
  • 来自: 湖南
社区版块
存档分类
最新评论

JavaScript Patterns 读书笔记(五)

    博客分类:
  • Web
阅读更多

5.Inheritance Pattern

  • Classical Pattern #1—The Default Pattern
        The default method most commonly used is to create an object using the Parent() constructor and assign this object to the Child()’s prototype. Here’s the first implementation of the reusable inherit() function:
    function inherit(C, P) {
    	C.prototype = new P();
    }
    
         It’s important to remember that the prototype property should point to an object, not a function, so it has to point to an instance (an object) created with the parent constructor, not to the constructor itself. In other words, pay attention to the new operator, because you need it for this pattern to work.Later in your application when you use new Child() to create an object, it gets functionality from the Parent() instance via the prototype, as shown in the following example:
    var kid = new Child();
    kid.say(); // "Adam"
    
     Drawbacks When Using Pattern #1
        One drawback of this pattern is that you inherit both own properties added to this and prototype properties. Most of the time you don’t want the own properties, because they are likely to be specific to one instance and not reusable.
        Another thing about using a generic inherit() function is that it doesn’t enable you to pass parameters to the child constructor, which the child then passes to the parent. Consider this example:
    var s = new Child('Seth');
    s.say(); // "Adam"
    
       This is not what you’d expect. It’s possible for the child to pass parameters to the parent’s constructor, but then you have to do the inheritance every time you need a new child, which is inefficient, because you end up re-creating parent objects over and over.

  • Classical Pattern #2—Rent-a-Constructor
        This next pattern solves the problem of passing arguments from the child to the parent.It borrows the parent constructor, passing the child object to be bound to this and also forwarding any arguments:
    function Child(a, c, b, d) {
    	Parent.apply(this, arguments);
    }
    
        This way you can only inherit properties added to this inside the parent constructor.You don’t inherit members that were added to the prototype.
        Using the borrowed constructor pattern, the children objects get copies of the inherited members, unlike the classical #1 pattern where they only get references. The following example illustrates the difference:
    // a parent constructor
    function Article() {
    	this.tags = ['js', 'css'];
    }
    var article = new Article();
    
    // a blog post inherits from an article object
    // via the classical pattern #1
    function BlogPost() {}
    BlogPost.prototype = article;
    var blog = new BlogPost();
    
    // note that above you didn't need `new Article()`
    // because you already had an instance available
    // a static page inherits from article
    // via the rented constructor pattern
    function StaticPage() {
    	Article.call(this);
    }
    var page = new StaticPage();
    
    alert(article.hasOwnProperty('tags')); // true
    alert(blog.hasOwnProperty('tags')); // false
    alert(page.hasOwnProperty('tags')); // true
    
     
       In this example the child blog object modifies the tags property, and this way it also        modifies the parent because essentially both blog.tags and article.tags point to the same array. Changes to page.tags don’t affect the parent article because page.tags is a separate copy created during inheritance.

    Multiple Inheritance by Borrowing Constructors
        Using the borrowing constructors patterns, it’s possible to implement multiple inheritance simply by borrowing from more than one constructor:
    function Cat() {
    	this.legs = 4;
    	this.say = function () {
    		return "meaowww";
    	}
    }
    
    function Bird() {
    	this.wings = 2;
    	this.fly = true;
    }
    
    function CatWings() {
    	Cat.apply(this);
    	Bird.apply(this);
    }
    
    var jane = new CatWings();
    console.dir(jane);
    
     
    Pros and Cons of the Borrowing Constructor Pattern
        The drawback of this pattern is obviously that nothing from the prototype gets inherited and, as mentioned before, the prototype is the place to add reusable methods and properties, which will not be re-created for every instance.
        A benefit is that you get true copies of the parent’s own members, and there’s no risk that a child can accidentally overwrite a parent’s property.

  • Classical Pattern #3—Rent and Set Prototype
        Combining the previous two patterns, you first borrow the constructor and then also set the child’s prototype to point to a new instance of the constructor:
    function Child(a, c, b, d) {
    	Parent.apply(this, arguments);
    }
    Child.prototype = new Parent();
    
        The benefit is that the result objects get copies of the parent’s own members and references to the parent’s reusable functionality (implemented as members of the prototype).
        The child can also pass any arguments to the parent constructor. This behavior is probably the closest to what you’d expect in Java; you inherit everything there is in the parent, and at the same time it’s safe to modify own properties without the risk of modifying the parent.
         A drawback is that the parent constructor is called twice, so it could be inefficient. At the end, the own properties (such as name in our case) get inherited twice.

    Let’s take a look at the code and do some testing:
    // the parent constructor
    function Parent(name) {
    	this.name = name || 'Adam';
    }
    // adding functionality to the prototype
    Parent.prototype.say = function () {
    	return this.name;
    };
    
    // child constructor
    function Child(name) {
    	Parent.apply(this, arguments);
    }
    Child.prototype = new Parent();
    
    var kid = new Child("Patrick");
    kid.name; // "Patrick"
    kid.say(); // "Patrick"
    delete kid.name;
    kid.say(); // "Adam"
    
        Unlike the previous pattern, now say() is inherited properly. You can also notice that name is inherited two times, and after we delete the own copy, the one that comes down the prototype chain will shine through.

  • Classical Pattern #4—Share the Prototype
        The rule of thumb was that reusable members should go to the prototype and not this. Therefore for inheritance purposes, anything worth inheriting should be in the prototype. So you can just set the child’s prototype to be the same as the parent’s prototype:
    function inherit(C, P) {
    	C.prototype = P.prototype;
    }
    
         This gives you short and fast prototype chain lookups because all objects actually share the same prototype. But that’s also a drawback because if one child or grandchild somewhere down the inheritance chain modifies the prototype, it affects all parents and grandparents.

  • Classical Pattern #5—A Temporary Constructor
        The next pattern solves the same-prototype problem by breaking the direct link between parent’s and child’s prototype while at the same time benefiting from the prototype chain.
        Below is an implementation of this pattern, where you have an empty function F(), which serves as a proxy between the child and the parent. F()’s prototype property points to the prototype of the parent. The prototype of the child is an instance of the blank function:
    function inherit(C, P) {
    	var F = function () {};
    	F.prototype = P.prototype;
    	C.prototype = new F();
    }
    
         This pattern has a behavior slightly different from the default pattern (classical pattern #1) because here the child only inherits properties of the prototype. And that’s usually fine, actually preferable, because the prototype is the place for reusable functionality. In this pattern, any members that the parent constructor adds to this are not inherited.

    Storing the Superclass
       Building on top of the previous pattern, you can add a reference to the original parent.        This is like having access to the superclass in other languages and could be handy on occasion.
       The property is called uber because “super” is a reserved word and “superclass” may lead the unsuspecting developer down the path of thinking that JavaScript has classes. Here’s an improved implementation of this classical pattern:
    function inherit(C, P) {
    	var F = function () {};
    	F.prototype = P.prototype;
    	C.prototype = new F();
    	C.uber = P.prototype;
    }
     
    Resetting the Constructor Pointer
         One last thing to add to this almost perfect classical inheritance function is to reset the pointer to the constructor function in case you need it down the road.
         If you don’t reset the pointer to the constructor, then all children objects will report that Parent() was their constructor, which is not useful. So using the previous implementation of inherit(), you can observe this behavior:
    // parent, child, inheritance
    function Parent() {}
    function Child() {}
    inherit(Child, Parent);
    
    // testing the waters
    var kid = new Child();
    kid.constructor.name; // "Parent"
    kid.constructor === Parent; // true
    
         The constructor property is rarely used but could be convenient for runtime introspection of objects. You can reset it to point to the expected constructor function without affecting the functionality because this property is mostly informational.
        The final Holy Grail version of this classical inheritance pattern will look like so:
    function inherit(C, P) {
    	var F = function () {};
    	F.prototype = P.prototype;
    	C.prototype = new F();
    	C.uber = P.prototype;
    	C.prototype.constructor = C;
    }
    
         A function similar to this exists in the YUI library (and probably other libraries) and brings the classical inheritance to a language without classes, if you decide that this is the best approach for your project.
         A common optimization of the Holy Grail pattern is to avoid creating the temporary (proxy) constructor every time you need inheritance. It’s sufficient to create it once and only change its prototype. You can use an immediate function and store the proxy function in its closure:
    var inherit = (function () {
    	var F = function () {};
    	return function (C, P) {
    		F.prototype = P.prototype;
    		C.prototype = new F();
    		C.uber = P.prototype;
    		C.prototype.constructor = C;
    	}
    }());
    
     Klass
           Many JavaScript libraries emulate classes, introducing new sugar syntax. The implementations differ but there are often some commonalities, including the following:

                • There’s a convention on how to name a method, which is to be considered the constructor of the class, for example initialize, _init, or something similar and which gets called automatically.

                • Classes inherit from other classes.

                • There’s access to the parent class (superclass) from within the child class.

         Without going into too much detail, let’s see an example implementation of simulated        classes in JavaScript. First, how will the solution be used from the client’s perspective?
    var Man = klass(null, {
    	__construct: function (what) {
    		console.log("Man's constructor");
    		this.name = what;
    	},
    	getName: function () {
    		return this.name;
    	}
    });
    
        The syntax sugar comes in the form of a function called klass(). In some implementations        you may see it as Klass() constructor or as augmented Object.prototype, but in this example, let’s keep it a simple function.
        The function takes two parameters: a parent class to be inherited and implementation of the new class provided by an object literal. Influenced by PHP, let’s establish the convention that the class’s constructor must be a method called __construct. In the preceding snippet, a new class called Man is created and doesn’t inherit from anything (which means inherit from Object behind the scenes). The Man class has an own property  name created inside __construct and a method getName(). The class is a constructor function, so the following will still work (and will look just like a class instantiation):
    var first = new Man('Adam'); // logs "Man's constructor"
    first.getName(); // "Adam"
    
     Finally, let’s see how the klass() function can be implemented:
    var klass = function (Parent, props) {
    	var Child, F, i;
    
    	// 1.
    	// new constructor
    	Child = function () {
    		if (Child.uber && Child.uber.hasOwnProperty("__construct")) {
    		      Child.uber.__construct.apply(this, arguments);
    		}
    		if (Child.prototype.hasOwnProperty("__construct")) {
    		      Child.prototype.__construct.apply(this, arguments);
    		}
    	};
    
    	// 2.
    	// inherit
    	Parent = Parent || Object;
    	F = function () {};
    	F.prototype = Parent.prototype;
    	Child.prototype = new F();
    	Child.uber = Parent.prototype;
    	Child.prototype.constructor = Child;
    
    	// 3.
    	// add implementation methods
    	for (i in props) {
    		if (props.hasOwnProperty(i)) {
    			Child.prototype[i] = props[i];
    		}
    	}
    	// return the "class"
    	return Child;
    };
    
     The klass() implementation has three interesting and distinct parts:

    1. A Child() constructor function is created. This is the function that will be returned at the end and will be used as a class. In this function the __construct method is called if it exists. Also before that the parent’s __construct is called (again, if it exists) using the static uber property. There might be cases when uber is not defined—when you inherit from Object for example, as the case was with the Man class definition.

    2. The second part takes care of the inheritance bit. It’s simply using the classical inheritance’s Holy Grail pattern discussed in the previous section of the chapter. There’s only one new thing: setting the Parent to Object if no Parent was passed to inherit from.

    3. The final section is looping through all the implementation methods (such as __construct and getName in the examples), which are the actual definition of the class and adding them to the prototype of Child.

        When to use such a pattern? Well, it’s actually better if you avoid it, because it brings the whole confusing notion of classes, which don’t technically exist in the language. It adds new syntax and new rules to learn and remember. That said, if you or the team feel at ease with classes and at the same time feel uncomfortable with prototypes, then this could be something to explore. This pattern allows you to forget about the prototypes completely, and the good thing is you can tweak the syntax and the conventions to resemble another of your favorite languages.

  • Prototypal Inheritance
        Let’s start the discussion of “modern” classless patterns with a pattern called prototypal inheritance. In this pattern there are no classes involved; here objects inherit from other objects. You can think about it this way: you have an object that you would like to reuse and you want to create a second object that gets its functionality from the first one.
    // object to inherit from
    var parent = {
    	name: "Papa"
    };
    // the new object
    var child = object(parent);
    // testing
    alert(child.name); // "Papa"
    
         In the preceding snippet, you have an existing object called parent created with the object literal, and you want to create another object called child that has the same properties and methods as the parent. The child object was created with a function called object(). This function doesn’t exist in JavaScript (not to be mistaken with the constructor function Object()), so let’s see how you can define it.
        Similarly to the classical Holy Grail, you would use an empty temporary constructor function F(). You then set the prototype of F() to be the parent object. Finally, you return a new instance of the temporary constructor:
    function object(o) {
    	function F() {}
    	F.prototype = o;
    	return new F();
    }
    
         In the prototypal inheritance pattern, your parent doesn’t need to be created with the literal notation (although that is probably the more common way). You can have constructor functions create the parent. Note that if you do so, both “own” properties and properties of the constructor’s prototype will be inherited:
    // parent constructor
    function Person() {
    	// an "own" property
    	this.name = "Adam";
    }
    // a property added to the prototype
    Person.prototype.getName = function () {
    	return this.name;
    };
    
    // create a new person
    var papa = new Person();
    // inherit
    var kid = object(papa);
    // test that both the own property
    // and the prototype property were inherited
    kid.getName(); // "Adam"
    
         In another variation of this pattern you have the option to inherit just the prototype object of an existing constructor. Remember, objects inherit from objects, regardless of how the parent objects were created. Here’s an illustration using the previous example, slightly modified:
    // parent constructor
    function Person() {
    	// an "own" property
    	this.name = "Adam";
    }
    // a property added to the prototype
    Person.prototype.getName = function () {
    	return this.name;
    };
    // inherit
    var kid = object(Person.prototype);
    
    typeof kid.getName; // "function", because it was in the prototype
    typeof kid.name; // "undefined", because only the prototype was inherited
    
     
    Addition to ECMAScript 5
           In ECMAScript 5, the prototypal inheritance pattern becomes officially a part of the language. This pattern is implemented through the method Object.create(). In other words, you won’t need to roll your own function similar to object(); it will be built into the language:
    var child = Object.create(parent);
         Object.create() accepts an additional parameter, an object. The properties of the extra object will be added as own properties of the new child object being returned. This is a convenience that enables you to inherit and build upon the child object with one method call. For example:
    var child = Object.create(parent, {
    	age: { value: 2 } // ECMA5 descriptor
    });
    child.hasOwnProperty("age"); // true
    
     
  • Inheritance by Copying Properties
       Let’s take a look at another inheritance pattern—inheritance by copying properties. In this pattern, an object gets functionality from another object, simply by copying it.Here’s an example implementation of a sample function extend() that does that:
    function extend(parent, child) {
    	var i;
    	child = child || {};
    	for (i in parent) {
    		if (parent.hasOwnProperty(i)) {
    			child[i] = parent[i];
    		}
    	}
    	return child;
    }
    
         It’s a simple implementation, just looping through the parent’s members and copying them over. In this implementation child is optional; if you don’t pass an existing object to be augmented, then a brand new object is created and returned:
    var dad = {name: "Adam"};
    var kid = extend(dad);
    kid.name; // "Adam"
    
        The implementation given is a so-called “shallow copy” of the object. A deep copy on the other hand would mean checking if the property you’re about to copy is an object or an array, and if so, recursively iterating through its properties and copying them as well. With the shallow copy (because objects are passed by reference in JavaScript), if you change a property of the child, and this property happens to be an object, then you’ll be modifying the parent as well.
        Now let’s modify the extend() function to make deep copies. All you need is to check if a property’s type is an object, and if so, recursively copy its properties. Another check you need is if the object is a true object or if it’s an array. Let’s use the check for arrayness discussed in Chapter 3. So the deep copy version of extend() would look like so:
    function extendDeep(parent, child) {
    	var i,
    	toStr = Object.prototype.toString,
    	astr = "[object Array]";
    	child = child || {};
    	for (i in parent) {
    		if (parent.hasOwnProperty(i)) {
    			if (typeof parent[i] === "object") {
    				child[i] = (toStr.call(parent[i]) === astr) ? [] : {};
    				extendDeep(parent[i], child[i]);
    			} else {
    				child[i] = parent[i];
    			}
    		}
    	}
    
    	return child;
    }
    
         Now testing the new implementation gives us true copies of objects, so child objects don’t modify their parents:
    var dad = {
    	counts: [1, 2, 3],
    	reads: {paper: true}
    	};
    var kid = extendDeep(dad);
    kid.counts.push(4);
    kid.counts.toString(); // "1,2,3,4"
    
    dad.counts.toString(); // "1,2,3"
    dad.reads === kid.reads; // false
    kid.reads.paper = false;
    
    kid.reads.web = true;
    dad.reads.paper; // true
    
         This property copying pattern is simple and widely used; for example, Firebug (Firefox extensions are written in JavaScript) has a method called extend() that makes shallowcopies and jQuery’s extend() creates a deep copy. YUI3 offers a method called Y.clone(), which creates a deep copy and also copies over functions by binding them to the child object. (There will be more on binding later in this chapter.) It’s worth noting that there are no prototypes involved in this pattern at all; it’s only about objects and their own properties.

  • Borrowing Methods
        Sometimes it may happen that you only like one or two methods of an existing object.You want to reuse them, but you don’t really want to form a parent-child relationship with that object. You want to use just the methods you like, without inheriting all the other methods that you’ll never need. This is possible with the borrowing methods pattern, which benefits from the function methods call() and apply(). You’ve seen this pattern already in the book and even in this chapter in the implementation of extendDeep(), for example.
       As you know, functions in JavaScript are objects, and they come with some interesting methods of their own, such as call() and apply(). The only difference between the two is that one takes an array of parameters to be passed to the method being called, and the other one takes parameters one by one. You can use these methods to borrow functionality from existing objects:
    // call() example
    notmyobj.doStuff.call(myobj, param1, p2, p3);
    // apply() example
    notmyobj.doStuff.apply(myobj, [param1, p2, p3]);
    
         Here you have an object called myobj and you know that some other object called notmyobj has this useful method called doStuff(). Instead of going through the inheritance hassle and inheriting a number of methods your myobj will never need, you can simply borrow the method doStuff() temporarily.
        You pass your object and any parameters, and the borrowed method binds your object as its own this. Basically, your object pretends to be the other object for a bit to benefit from the method you like. It’s like getting an inheritance but without paying the inheritance tax (where the “tax” comes in the form of extra properties and methods you have no need for).

    Example: Borrow from Array
       A common use for this pattern is borrowing array methods.Arrays have useful methods, which array-like objects such as arguments don’t have. So arguments can borrow array methods, such as the method slice(). Here’s one example:
    function f() {
    	var args = [].slice.call(arguments, 1, 3);
    	return args;
    }
    // example
    f(1, 2, 3, 4, 5, 6); // returns [2,3]
    
     
    Borrow and Bind
          When borrowing methods either through call()/apply() or through simple assignment,        the object that this points to inside of the borrowed method is determined based on the call expression. But sometimes it’s best to have the value of this “locked” or bound to a specific object and predetermined in advance(预先决定调用者是谁).
       Let’s see an example. There’s an object called one that has a say() method:
    var one = {
    	name: "object",
    	say: function (greet) {
    		return greet + ", " + this.name;
    	}
    };
    // test
    one.say('hi'); // "hi, object"
    
     
    Now another object two doesn’t have a say() method, but it can borrow it from one:
      
    var two = {
    	name: "another object"
    };
    one.say.apply(two, ['hello']); // "hello, another object"
    
        In the preceding case, this inside say() pointed to two and this.name was therefore “another object.” But what about scenarios in which you assign the function pointer to a global variable or you pass the function as a callback? In client-side programming there are a lot of events and callbacks, so that does happen a lot:
    // assigning to a variable
    // `this` will point to the global object
    var say = one.say;
    say('hoho'); // "hoho, undefined"
    
    // passing as a callback
    var yetanother = {
    	name: "Yet another object",
    	method: function (callback) {
    		return callback('Hola');
    	}
    };
    yetanother.method(one.say); // "Holla, undefined"
    
        In both of those cases this inside say() was pointing to the global object, and the whole snippet didn’t work as expected. To fix (in other words, bind) an object to a method,we can use a simple function like this:
    function bind(o, m) {
    	return function () {
    		return m.apply(o, [].slice.call(arguments));
    	};
    }
    
         This bind() function accepts an object o and a method m, binds the two together, and then returns another function. The returned function has access to o and m via a closure.Therefore even after bind() returns, the inner function will have access to o and m, which will always point to the original object and method. Let’s create a new function using bind():
    var twosay = bind(two, one.say);
    twosay('yo'); // "yo, another object"
    
        As you can see, even though twosay() was created as a global function, this didn’t point to the global object, but it pointed to object two, which was passed to bind(). Regardless of how you call twosay(), this will always be bound to two. The price you pay for the luxury of having a bind is the additional closure.

    Function.prototype.bind()
        ECMAScript 5 adds a method bind() to Function.prototype, making it just as easy to  use as apply() and call(). So you can do expressions like:
    var newFunc = obj.someFunc.bind(myobj, 1, 2, 3);
        This means bind together someFunc() and myobj and also prefill the first three arguments that someFunc() expects.Let’s see how you can implement Function.prototype.bind() when your program runs in pre-ES5 environments:
    if (typeof Function.prototype.bind === "undefined") {
    	Function.prototype.bind = function (thisArg) {
    		var fn = this,
    		slice = Array.prototype.slice,
    		args = slice.call(arguments, 1);
    		return function () {
    			return fn.apply(thisArg, args.concat(slice.call(arguments)));
    		};
    	};
    }
    
        This implementation probably looks a bit familiar; it’s using partial application and concatenating the list of arguments—those passed to bind() (except the first) and those passed when the new function returned by bind() is called later. Here’s an example use:
    var twosay2 = one.say.bind(two);
    twosay2('Bonjour'); // "Bonjour, another object"
     
  • Last
       Remember that code reuse is the goal, and inheritance is just one of the ways to accomplish that goal.

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics