js原型、原型链与继承的理解

链接1:https://www.cnblogs.com/gulei/p/6733707.html
链接2:https://www.cnblogs.com/DF-fzh/p/5619319.html(比较推荐)
链接3:https://www.cnblogs.com/chuaWeb/p/5039232.html(比较推荐)
链接4:http://blog.csdn.net/alex8046/article/details/51940518(比较推荐)
不懂的可以查阅这几个链接

参考书目:《js高级程序设计》第三版
第六章6.2节P144-P174

个人建议先看《js高级程序设计》第三版,然后再看链接理解

创建对象(类)的几种模式

1.工厂模式

function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson('Nicholas', 29, 'Software Engineer');
var person2 = createPerson('Greg', 27, 'Doctor');
工厂模式解决了创建多个相似对象的问题,但没有解决对象识别的问题。

2.构造函数模式

function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person('Nicholas', 29, 'Software Engineer');
var person2 = new Person('Greg', 27, 'Doctor');
按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。要创建Person的新实例,必须使用new操作符。
任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样。

3.原型模式

(1)原型对象(protoytpe)

理解原型对象:

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认的情况下,所有原型对象都会自动获得一个constuctor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。如下图:


扫描二维码关注公众号,回复: 915748 查看本文章

function Person(){
Person.prototype.name = ''Nicholas'';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function(){
alert(this.name);
};
}
var person1 = new Person();
person1.sayName(); //''Nicholas''
var person2 = new Person();
person2.sayName(); //''Nicholas''
alert(person1.sayName == person2.sayName);//true


当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。
function Person(){
Person.prototype.name = ''Nicholas'';
Person.prototype.age = 29;
Person.prototype.job = 'Software Engineer';
Person.prototype.sayName = function(){
alert(this.name);
};
}
var person1 = new Person();
var person2 = new Person();
person1.name = 'Greg';
alert(person1.name);    //'Greg'来自实例
alert(person2.name) //'Nicholas'来自原型

为了更好的封装代码,重写整个原型对象:
function Person(){
Person.prototype = {
name : ''Nicholas'',
age : 29,
job : 'Software Engineer',
sayName : function(){
alert(this.name);
};
}
}
大家思考一下,这里的constructor属性指向谁?还是指向原来的Person吗?
前面介绍过,每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor属性。
所以这里的constructor属性应该指向谁?

大家可以从一下代码可以得出答案:
var friend = new Person();
alert(friend.constructor == Person);//false
alert(friend.constructor == Object);//true

所以这里的constructor属性应该指向Object


如果constructor 的值真的很重要,可像下面这样特意将它设置回适当的值。
function Person(){
Person.prototype = (){
constructor  : Person,
name : ''Nicholas'',
age : 29,
job : 'Software Engineer',
sayName : function(){
alert(this.name);
};
}
}

但是要注意,这种方式重设constructor属性会导致它的[[Enumerable]]特性被设置为true。但是默认情况下,原生的constructor属性是不可枚举的,也就是[[Enumerable]]特性默认为false。(更多信息大家可查阅《js高级程序设计》第三版)

下面做一道题加强记忆
function A(){
this.do = function(){return 'foo'}
}
A.prototype = function(){
this.do = function(){return 'bar'}
}
var x = new A().do();
A的值为多少?

这里我们介绍完了原型,但是做这一道题我们是需要原型链的知识才行的。这题的答案是foo。为什么?可以先把原型链的知识看完再回头来理解这道题吧。

(2)原型与in操作符(大家可自行看书理解)

(3)更简单的原型语句(大家可自行看书理解)

(4)原型的动态性(大家可自行看书理解)

(5)原生对象的原型(大家可自行看书理解)

(6)原型对象的问题(大家可自行看书理解)

4.组合使用构造函数模式和原型模式

        创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。

这样创建的结果好处是,每个实例都会有自己的一份实例属性的副本,同时又共享着对方法的引用,最大限度的节省了内存,另外这种混成模式还支持向构造函数传递参数。

下面来看一段代码去理解

function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ['Shelby', 'Court'];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person('Nicholas', 29, 'Software Engineer');
var person2 = new Person('Greg', 27, 'Doctor');
person1.friends.push('Van');
alert(person1.friends); //'Shelby, Court, Van'
alert(person2.friends); //'Shelby, Court'
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

5.动态原型模式

6.寄生构造函数模式

7.稳妥构造函数模式


继承

1.原型链

在谈原型链之前,我们首先要了解自定义函数与 Function 之间是什么关系,而构造函数、原型和实例之间又存在什么千丝万缕的关系呢?其实,所有的函数都是 Function 的实例。在构造函数上都有一个原型属性 prototype,该属性也是一个对象;那么在原型对象上有一个 constructor 属性,该属性指向的就是构造函数;而实例对象上有一个 _proto_  属性,该属性也指向原型对象,并且该属性不是标准属性,不可以用在编程中,该属性用于浏览器内部使用。
function Foo(age){
this.age = age;
}
Foo.prototype = {
constructor : Foo,
showInfo : function(){
alert(this.info);
}
}
var Foo1 = new Foo();
Foo1.info = 'hi';
alert(Foo1.info); //hi
var Foo2 = new Foo();
Foo2.info = 'hello'; //hello
alert(Foo2.info);

alert(Foo1.showInfo == Foo2.showInfo); //true

看完上述代码,我们可以画出原型链图


很明显的从原型链当中我们可以知道,当实力化一个对象的时候,而且调用属性和方法的时候,它是先搜索原型链的起始端,然后一路往上查找,直到原型链的末端,也就是最终指向null。

这一题先打印的是Foo1.info,那么它首先查找哪里呢?首先是查找实例化的对象自己本身,也就是第一轮查找,发现它自己已经添加了一个属性info,并且值为hi,也就是该句代码:Foo1.info = 'hi';哎,很幸运的是第一轮就查找到了,也不再向上查找了,所以打印出来的值为hi;但是Foo2.info就不一样了,它的查找顺序一样,先找自己有没有info这个属性,发现没有,再往上查找,找构造函数的原型对象,发现里面有一个属性名为info,且值为hello,哎,从构造函数的原型对象上查找到了,就不再向上查找了,于是打印的值为hello。然后Foo1和Foo2方法的调用是一样的,因为实例化对象自己本身是没有方法的,两者都在构造函数的原型对象中找到的showInfo方法,所以打印出的值为true。

下面再来看一张图,进一步理解原型链


这个现在大家都能明白了吧?

原型链其实就是有限的实例对象和原型之间组成有限链,就是用来实现共享属性和继承的。再来看一段代码:
function Foo1(){
   this.name1 = '1';
}
function Foo2(){
   this.name2 = '2';
}
Foo2.prototype = new Foo1();
function Foo3(){
   this.name = '3';
}
Foo3.prototype = new Foo2();
var foo3 = new Foo3();
console.dir(foo3);
//console.dir()可以显示一个对象所有的属性和方法。

大家可以尝试画一下这段代码的原型链


大家可以通过看代码,一级一级的往上找,最终的原型链图如上。

下面再来做一道面试题加强理解与记忆,题目如下:

                var A = {n:4399};
var B = function(){this.n=9999};
var C = function(){var n=8888;};
B.prototype = A;
C.prototype = A;
var b = new B();
var c = new C();
A.n++;
console.log(b.n);
console.log(c.n);

首先刚开始的时候大家看这道题也许有点懵,不知道从哪下手,但是笔者给大家一个建议,就是做有关原型链的题目时,一定要记住,实例化对象在调用属性或者方法的时候,一直要记得是从原型链的开头开始搜索,一级一级往上递进,一直搜索到对象原型的原型为止,即为null。

那么我首先来看b.n的值到底应该为多少?

首先看b,b是构造函数B的实例化对象,然后b调用的是n属性,那么我们先看实例化对象b它自己是否有n的属性,我们看完代码发现没有,继续往上查找,查找B的原型对象(B.prototype),发现B里面有一个this.n=9999,发现有n的属性,并且值为9999,所以b.n=9999。这里或许有同学有疑问了,上面不是把A赋值给B.prototype了吗?怎么不是调用A的值呢?如果有同学这样想的话,那么说明你还是没有完全明白原型链,我在上面也说过:每创建一个函数,就会同时创建它的prototype对象,这个对象也会自动获得constructor属性。现在可以这样理解B.prototype = A这句代码,是B.prototype创建了一个对象A,B.prototype又获得了一个prototype属性,同时A也获得了constructor属性,而这个constructor属性指向B.prototype。不知道大家反应过来没有,所以,如果B.prototype中没有n这个属性,才会继续再向上查找A.prototype。即b.n向上搜索的原型链应该为b._photo_ > B.prototype > A.prototype > Object.prototype > null。这题是在B.prototype 就查找到了n这个属性,所以b.n=9999.

再来看c.n,首先看实例化对象自己本身是否有n的属性,发现也没有,向上搜索,搜索C.prototype发现也没有,有的同学又会问了,里面不是有一个var n=8888;吗?

但是这里我想告诉大家的是,这里的n是一个局部变量而非属性!!!人家对象属性的定义是this.name = '3';这样的

或者是name:"3"这样的,所以C.prototype是没有的!继续向上查找,发现A.prototype里有n的属性,值为4399,所以最后打印

的值应该为4399+1=4400,即c.n=4400.(注意:这里若是还有同学不懂,没关系,可以参考链接:点击打开

让你分分钟明白对象里的私有属性和私有方法,公有属性和公有方法,静态属性和静态方法的使用方法!


2.借用构造函数(大家可自行看书理解)

3.组合继承

    组合继承指的是将原型链和借用构造函数的技术组合到一块,从而发挥二者之长的一种继承模式。其背后的思路是使用原型链实现对原型属性和方法的继承,而通过借用构造函数来实现对实例属性的继承。

下面来看代码例子进一步理解:

function SuperType(name){
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name, age){
//继承属性
SuperType.call(this.name);
this.age = age;
}
//继承方法
SubType.prototype = new SuperType();
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance1 = new SubType('Nicholas', 29);
instance1.colors.push('black');
alert(instance1.colors); //'red, blue, green, black'
instance1.sayName(); //'Nicholas'
instance1.sayAge(); //29
var instance2 = new SubType('Greg', 27);
alert(instance2.colors); //'red, blue, green'
instance2.sayName(); //'Greg'
instance2.sayAge(); //27

4.原型式继承(大家可自行看书理解)
5.寄生式继承(大家可自行看书理解)
6.寄生组合式继承(大家可自行看书理解)

猜你喜欢

转载自blog.csdn.net/charles_tian/article/details/79718488