前言
最近呢,又把 javascript高级程序设计(第三版)中原型、原型链读了一遍。原来读这一部分,一步一步的跟着书中对例子的分析,似懂非懂的点着头。哦!原来这样!
现在已经工作一年了,在面试的时候被问到的频率也是相当大,而我也是知其然而不知其所以然。有笔试遇到这个题,干脆画一个原型链的图,心里想着:“面试官您就看图自己意会吧”!霍霍,其实是自己用文字描述不出来***^_^***
构造函数、原型、实例之间的关系
要说原型链,那就要搞清楚构造函数、原型和实例之间的关系。书中有对他们之间的关系做简要概括,但是我在刚开始看这部分的时候,对这里的理解很是费劲,最近有个朋友也问到我这部分,说她读到这里就读不懂了。书中说“每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针”。
什么是构造函数
那什么是构造函数呢?在javascript中Object、Array都是构造函数(原生构造函数)。构造函数可以用来创建特定类型的对象,像Object、Array就是特定的类型对象。另外构造函数的命名要以大写字母开头,这是借鉴了其他OO(Object Oriented,面向对象)语言,主要是为了区别其他函数。创建构造函数的实例必须要使用new操作符,这也是构造函数与其他函数的唯一区别,否则这个构造函数跟其他普通函数也无异了。
function Person(){} //定义一个空构造函数 var person = new Person(); //创建构造函数的一个实例,person指向构造函数的作用域
什么是原型对象
每个函数都有一个原型对象。函数的prototype属性指向这个函数的原型对象,所有的原型对象也都会自动获得一个constructor属性,并且这个属性包含的指针指向prototype所在函数。也就是说,在创建一个函数的时候,这个函数的原型对象也就被创建了,prototype指向这个原型。
比如上面创建的Person构造函数,Person.prototype指向Person的原型对象,Person.prototype.construbtor又指向Person函数
什么是实例
实例实例,那不就是一个实实在在的例子!!!我们经常会听到“实例化一个对象”这句话。上面代码中new Person()就是实例化了一个对象,person就是一个实例,一个实实在在的例子,一个人。只不过这个人是我们不认识的一个人,没有年龄,没有姓名,没有性别等等。这不是我们要讨论的事情,现在我们再说实例与构造函数,原型对象之间的关系。
当创建person这个实例的时候,同时也被创建了一个指向构造函数原型对象的指针,这个指针叫[[Prototype],]注意是指向构造函数的原型,而不是函数本身!!!!!在浏览器中我们打开控制台的时候可以看到-proto-属性,这个属性就是指向原型对象的指针。
经过分析之后再重读那句话:每个构造函数(Person)都有一个原型对象(Person.prototype),原型对象都包含一个指向构造函数的指针(这个指针叫constructor,是在创建构造函数的时候原型对象自动获取的),而实例(person)都包含一个指向原型对象的内部指针(这个指针叫[[Prototype]],我们没法访问这个指针)。搞清楚这三者之间的关系,我们再看原型链那就很容易理解了。
谈谈原型链继承
书中把原型链放到继承这一节来讲,说明原型链可以实现继承。并且还是实现继承的主要方法。原理就是:利用原型对象让一个引用类型继承另一个引用类型的属性和方法。
那么我们要怎么利用原型对象来实现继承呢?从上面 “什么是实例 ” 这一小节的图可以看出来,实例是指向原型对象的,既然这样,那这个实例就可以访问到原型和构造函数中的属性和方法。因为实例的指针指向原型,而原型的constructor属性包含的指针又指向构造函数。一层一层的通过向上查找,最终访问到构造函数与其原型的属性和方法。
现在假如有个超类SuperType和一个子类SubType,子类要继承父类的属性和方法,正如上面所提到的,实例指向原型对象,从而能访问其属性和方法。那我们把子类的原型对象作为超类SuperType的一个实例,这样子类SubType就能访问到超类的属性和方法。
function SuperType () { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; }; function SubType () { this.subproperty = false; } SubType.prototype = new SuperType(); //子类SubType的原型对象是超类SuperType的实例,这句是实现继承的关键一句 SubType.prototype.getSubValue=function(){ return this.subproperty; }; var instance = new SubType(); //子类的实例instance
运行上面那段代码,打开控制台。从监听实例instance开始,一层一层往下看。我们可以看到实例的指针指向SubType的原型,包含一个原型方法getSubvalue和超类的属性property(这是通过原型链访问到的),还有一个指针指向超类SuperType的原型对象。可以看到SuperType原型包含一个自动获取的constructor属性,属性中的指针指向构造函数SuperType,还有一个超类原型方法getSuperValue和一个指向Object原型的指针。
就这样,通过将超类的实例赋值给子类的原型,从而实现了继承。这个继承实现的本质其实是重写子类的原型对象。以上关系可用下图表示:
因为子类SubType的原型是超类的实例,所以,子类的原型指向超类的原型的指针是实例的指针[[Prototype]]。这样,子类除了拥有自己的属性和方法之拥有超类的属性和方法。而他们之间的关系是:子类实例(instance)指向子类的原型(SubType.prototype),子类的的原型又指向超类的原型(SuperType.prototype)。
定义方法注意点
如果要用原型链实现继承,那么在给子类定义方法时有两点需要注意:
一、一定要在替换原型语句之后给子类原型定义方法
这是因为实现继承的本质就是重写子类的原型,如果在替换原型语句之前给子类定义方法,那么这些方法都被定义在原始原型上了,重写原型之后,定义的这些方法将无法访问。
function SuperType () { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; }; function SubType () { this.subproperty = false; } SubType.prototype = new SuperType(); //这一句就是替换原型语句
SubType.prototype.getSubValue=function(){ //给子类定义原型方法getSubValue return this.subproperty; };
var instance = new SubType();
二、给子类定义原型方法不能使用对象字面量
对象字面量也是一个对象,如果使用它给子类定义方法将会导致继承失效。因为这样做对破坏子类与超类之间的连接关系,子类原型指针不再指向超类原型,而是指向了Object对象。
function SuperType () { this.property = true; } SuperType.prototype.getSuperValue = function() { return this.property; }; function SubType () { this. = false; } SubType.prototype = new SuperType(); //这一句就是替换原型语句 //使用对象字面量定义原型方法,会导致继承失败,上一行代码无效 SubType.prototype = { getSubValue : function (){ return this.subproperty; } }; var instance = new SubType();
不用对象字面量定义原型方法
使用对象字面量定义原型方法
原型链的缺点
原型链的缺点主要有两点:
一、包含引用类型值的原型属性会被所有实例共享,其中一个实例修改了引用类型的属性,会导致其他实例中的这个属性也被改变。例如:
function SuperType(){ this.color=['red','blue']; } function SubType(){ } //继承SuperType SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.color.push("green"); console.log(instance1.color); //"red,blue,green" var instance2 = new SuperType(); console.log(instance2.color) //"red,blue,green"
二、在创建子类实例的时候,不能向超类的构造函数传参。由于所有的子类都共享超类的属性,所以给超类传参不能保证不影响其他子类
由于以上两点缺点,在实际开发中很少单独使用原型链。