Expected Outcome When Using Classical Inheritance
the goal of implementing classical inheritance is to have objects created by one constructor Child() get properties that come from another constructor Parent().
Here's an example of defining the two constructors Parent() and Child():
//the parent constructor function Parent(name){ this.name=name||'Adam'; } //adding functionality to the prototype Parent.prototype.say=function(){ return this.name; } //empty child constructor function Child(name){} //inheritance magic happens here inhreit(Child,Parent);
Here you have the parent and child constructors,a method say() added to the parent constructor's prototype,and a call to a function called inherit() that takes care of the inhreitance.The inhreit() function is not provided by the function,so you have to implement it yourself.Let's see serveral approaches to implementing it in a generic way.
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"
Following the Prototype Chain
Using this pattern you inherit both own properties(instance-specific properties added to this,such as name) and prototype properites and methods(such as say()).
Let's review how the prototype chain works in this inheritance pattern.For the purposes of this discussion,let's think of the objects as blocks somewhere in memory,which can contain data and references to other blocks.When you create an object using new Parent(),you create one such block(marked block #2 on Figrue 6-1).It holds the data for the name property.If you attempt to access the say() method.though (for example,using (new Parent).say()),block #2 doesn't contain that method.But using the hidden link _proto_ that points to the prototype property of the constructor function Parent(),you gain access to object #1(Parent.prototype),which does konw about say() method.All that happens behind the scenes and you don't need to worry about it,but it's important to know how it works and where the data you're accessing or maybe modifying is.Note that __proto__ is used here to explain the prototype chain;this property is not available in the language itself,although it's provided in some environments(for example,Firefox).
Now let's see what happens when a new object is created using var kid=new Child() after using the inherit() function. The diagram ishown on Figure 6-2.
The Child() constructor was empty and no properties were added to Child.prototype;therefore,using new Child() creates objects that are pretty much empty,except for the hidden link __proto__.In this case,__proto__ points to the new Parent() object created in the inherit() function.
Now what happens when you do kid.say()?Object #3 doesn't have such a method,so it looks up to #2 via the prototype chain.Object #2 doesn't have it either,so it follows the chain up to #1,which does happen to have it.Then inside say() there's a reference to this.name which needs to be resolved.The lookup starts again.In this case this point to object #3,which doesn't have name.Object #2 is consulted and it does have a name property,with the value 'Adam.'
Finally,Let's take a look at one more step.Let's say we have this code:
var kid = new Child(); kid.name = "Patrick"; kid.say(); // "Patrick"
Figrue 6-3 shows how the chain will look in this case
Setting the kid.name doesn’t change the name property of object #2, but it creates an own property directly on the kid object #3. When you do kid.say(), the method say is looked up in object #3, then #2, and then finally found in #1, just as before. But this time looking up this.name (which is the same as kid.name) is quick, because the property is found right away in object #3.
If you remove the new property using delete kid.name, then the name property of object #2 will “shine through” and be found in consecutive(连续的, 始终一贯的, 联贯的) lookups.
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,becasue 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 chid 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,what is inefficient,because you end p 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 borrwos 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 article object via the classical pattern #1 function BlogPost(){} BlogPost.prototype=article; var blog=new BlogPost(); //note that above you didn't need 'new Aritcle()' // because you already had an instance avaible // a static page inherits from article via the rented constructor pattern //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 sinppet,the parent Article() is inherited in two ways.The default pattern causes the blog object to gain access to the tags property via the prototype,so it doesn't have it as an own property and hasOwnProperty() returns false.The page object has an own tags property because using the returned constructor the new object got a copy of (not a reference to ) the parent's tags member.
Note the difference when modifying the inherited tags property:
blog.tags.push('html'); page.tags.push('php'); alert(article.tags.join(', ')); // "js, css, html"
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.
The Prototype Chain
Let's take a look at how the prototype chain looks when using this pattern and the familiar Parent() and Child() constructors.Child() will be slightly modified to follow the new pattern:
// the parent construcotr 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); } var kid=new Child('Patrick'); kid.name; // 'Patrick' typeof kid.say; // "undefined"
If you take a look at Figure 6-4,you'll notice that there's no longer a link between the new Child object and the Parent.That's because Child.prototype was not used at all,and it simply points to a blank object.Using this pattern,kid got its own property name,but the say() method was never inherited,and an attempt to call it will result in an error.The inheritance was a one-off action that copied parent's own properties as child's own properties and that was about it; no __proto__ links were kept.
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 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,thw own properties(such as name in our case) get inherited twice.
// 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.
Figure 6-6 shows how the relationships between the objects work
Classical Pattern #4—Share the Prototype
Unlike the previous classical inheritance pattern,which required two calls to the parent constructor
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 protoype 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 chian modifies the protoype,it affects all parents and grandparents.
As Figrue 6-7 shows,both child and parent objects share the same protoype and get equal access to the say() method.However,children objects don't inherit the name property.
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 protoype 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 protoype 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 protoype(see Figure 6-8)
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.
Let's create a new child object and inspect its behavior:
var kid=new Child();