【寒暄】好久没有更新博客了,说来话长,因为我下定决心要从一个后台程序员转为Front End,其间走过了一段漫长而艰辛的时光,今天跟大家分享下自己对javascript中原型链继承的理解。
总的说来,js中的常用的继承方式可以分为两种,一种是原型链式继承,这也是本文要谈的重点;另外一种是借用构造函数继承,这方面的理解,我将在下次的博客中更新。好了,闲话不多说,进入正题。
一,关于原型
1 function Person(){}//这里我们声明一个函数Person,js中函数是对象,也是构造函数 2 console.log(Person.prototype)//打印一下Person对象的原型,会出现什么呢?如下图所示:
大家在图中看到了,Person对象的原型拥有一个constructor,它指向Person的构造函数,即Person本身,另外一个属性是__proto__属性,这个属性我会在后文中说明。
到这里,大家肯定会明白了,一个对象建立后,会产生一个局部的“小链式结构”,即Person对象拥有一个prototype属性,这个属性指向原型对象,在原型对象中又有一个构造器constructor,指向构造函数。用一张图来说明:
那么,原型对象的作用是什么呢?这个原型对象包含由特定类型的实例共享的属性和方法。大家要注意共享这两个字,用一段代码解释下
1 function Person(){ 2 this.name="bob" //这是一个实例属性 3 } 4 Person.prototype.eat=function(){ //给对象的原型对象添加一个eat的方法,接下来,new的实例会共享这个方法 5 return "food"; 6 } 7 var p1=new Person(); //这里究竟发生生了什么? 8 p1.eat()//->food 9 var p2=new Person(); 10 p2.eat()//->food,所以只要是Person的对象,他们都会共享原型对象的方法,当然,p1.name也会共享Person的实例属性,因为p1是Person的一个实例
好了,到这里原型的概念我们已经讲完了,大家或许会疑问,上面的new一个Person实例的过程中究竟发生了什么呢?为什么这个实例能够访问到原型对象中的方法?其实,在这个过程过程中,p1实例拥有了一个指针,这个指针指向构造函数的原型对象。此时原型对象中的方法自然能够被实例所访问。用一张图来说明下:
这里,我们总结下构造函数、原型和实例的关系:每个构造函数都有一个原型对象,原型对象拥有一个指向构造函数的指针,而实例拥有一个指向原型对象的内部指针(这就是前面所提到的[[Prototype]],即__proto__,要注意的是这个__proto__属性在chrome浏览器中是可以看到的,而在大部分浏览器是隐藏的!)
二,关于原型链继承
好了,说了这么多终于到回到我们的主角了【原型链】,提出一个思考:如果我们让原型对象等于另外一个对象的实例,将会有一个什么样的结果呢?先看下面一段代码
1 function Person(){ 2 this.name="bob"; 3 } 4 Person.prototype.eat=function(){ 5 return "food"; 6 } 7 function Student(){} 8 Student.prototype=new Person();//将Person实例赋给Student的原型对象 9 var one=new Student(); 10 one.name//bob 11 one.eat()//food,Student的实例能访问到Person对象的实例方法,也能访问到其原型属性中的方法
以上就是原型链继承的一种基本模式,那么我们怎么解释这样的原理呢?之前说过,对象的实例拥有一个指向原型对象的指针,那么student的原型对象拥有了Person对象实例后,自然也拥有一个指向Person原型对象的指针。此时,我们再new一个Student实例one时,one实例包含一个指向Student原型的指针,而Student.prototype拥有一个指向Person原型对象的指针,Person原型本身包含一个指向自身构造函数的指针。这样一来,就构成了实例与原型的链条。这就是所谓的原型链的概念!
用一张图描绘一下上面讲的情况:
大家要注意一下,这里的one对象的constructor现在指向谁呢?它并不指向Student,因为Student的原型指向另一个对象--Person的原型,而这个原型对象的constructor指向的是Person。
三,原型链方法的改写及注意的问题
function Person(){ this.name="bob" ; } Person.prototype.eat=function(){ return "food"; } function Student(){} //Student.prototype.eat=function(){ // return "food1"; //} //注意如果更改原型语句的代码放在替换之前,那么下面one.eat()的结果将仍然是food //,原因很简单,前面对prototype对象的修改,在后面的替换一句中被Person实例对象覆盖了 //,换句话说,就是现在的prototype实例中仍旧是以前的eat方法 Student.prototype=new Person(); Student.prototype.eat=function(){ return "food1"; } var one=new Student(); console.log(one.eat());//food1
1 function Person(){ 2 this.name="bob" ; 3 } 4 Person.prototype.eat=function(){ 5 return "food"; 6 } 7 function Student(){} 8 9 Student.prototype=new Person(); 10 Student.prototype={ 11 run:function(){ 12 return "run"; 13 } 14 }; 15 var one=new Student(); 16 console.log(one.eat());//Uncaught TypeError: undefined is not a function
1 function Person(){ 2 this.name="bob" ; 3 } 4 Person.prototype.eat=function(){ 5 return "food"; 6 } 7 function Student(){} 8 9 Student.prototype=new Person(); 10 Student.prototype.constructor=Student;//把Student原型对象中原本指向Person构造函数的对象强行指向到Student 11 var one=new Student(); 12 console.log(one.eat());//food
四,如何确定原型和实例关系
1 function Person(){ 2 this.name="bob" ; 3 } 4 Person.prototype.eat=function(){ 5 return "food"; 6 } 7 function Student(){} 8 9 Student.prototype=new Person(); 10 Student.prototype.constructor=Student; 11 var one=new Student(); 12 var person=new Person(); 13 console.log(one instanceof Student);//true 14 console.log(one instanceof Person);//true 15 console.log(person instanceof Person);//true 16 console.log(person instanceof Student);//false
function Person(){ this.name="bob" ; } Person.prototype.eat=function(){ return "food"; } function Student(){} Student.prototype=new Person(); var one=new Student(); var person=new Person(); console.log(Student.prototype.isPrototypeOf(one));//true console.log(Person.prototype.isPrototypeOf(one));//true console.log(Person.prototype.isPrototypeOf(person));//true console.log(Student.prototype.isPrototypeOf(person));//false