JavaScript Patterns 读书笔记(五)
5.Inheritance Pattern
Classical Pattern #1—The Default Patternfunction 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
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.
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.
// a parent constructorfunction Article() {this.tags = ['js', 'css'];}var article = new Article();// a blog post inherits from an article object// via the classical pattern #1function 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 patternfunction StaticPage() {Article.call(this);}var page = new StaticPage();alert(article.hasOwnProperty('tags')); // truealert(blog.hasOwnProperty('tags')); // falsealert(page.hasOwnProperty('tags')); // true?
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);?
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 parent constructorfunction Parent(name) {this.name = name || 'Adam';}// adding functionality to the prototypeParent.prototype.say = function () {return this.name;};// child constructorfunction 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.
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.
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.
function inherit(C, P) {var F = function () {};F.prototype = P.prototype;C.prototype = new F();C.uber = P.prototype;}?
// parent, child, inheritancefunction Parent() {}function Child() {}inherit(Child, Parent);// testing the watersvar 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.
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.
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
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.
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 constructorChild = 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.// inheritParent = Parent || Object;F = function () {};F.prototype = Parent.prototype;Child.prototype = new F();Child.uber = Parent.prototype;Child.prototype.constructor = Child;// 3.// add implementation methodsfor (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:
// object to inherit fromvar parent = {name: "Papa"};// the new objectvar child = object(parent);// testingalert(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.
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 constructorfunction Person() {// an "own" propertythis.name = "Adam";}// a property added to the prototypePerson.prototype.getName = function () {return this.name;};// create a new personvar papa = new Person();// inheritvar kid = object(papa);// test that both the own property// and the prototype property were inheritedkid.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 constructorfunction Person() {// an "own" propertythis.name = "Adam";}// a property added to the prototypePerson.prototype.getName = function () {return this.name;};// inheritvar kid = object(Person.prototype);typeof kid.getName; // "function", because it was in the prototypetypeof kid.name; // "undefined", because only the prototype was inherited?
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?
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.
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; // falsekid.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.
// call() examplenotmyobj.doStuff.call(myobj, param1, p2, p3);// apply() examplenotmyobj.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.
function f() {var args = [].slice.call(arguments, 1, 3);return args;}// examplef(1, 2, 3, 4, 5, 6); // returns [2,3]?
var one = {name: "object",say: function (greet) {return greet + ", " + this.name;}};// testone.say('hi'); // "hi, object"?
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 objectvar say = one.say;say('hoho'); // "hoho, undefined"// passing as a callbackvar 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.
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"?