继承小结
//父类 function Father(){
this.width=10;
this.data=[1,2,3];
this.key="this is Father";
} //子类 function Child(){} //方法1 Child.prototype=new Father(); //方法2 Child.prototype=Father.prototype; //方法3 Child.prototype=Object.create(Father.prototype); Child.prototype.view=function(){ //do something }
方法1:
- Child的原型指向Father的一个实例,不管创建多少个Child对象实例,它们的原型都指向同一个实例;
- 子类会继承父类实例属性,原型链查找效率提高,能为一些属性提供合理的默认值;
- 父类实例属性为引用类型时,不恰当地修改会导致所有子类被修改
- 子类实例化对象的constructor属性指向错误。
- 创建父类实例作为子类原型时,可能无法确定构造函数需要的合理参数,这样提供的参数继承给子类没有实际意义,当子类需要这些参数时应该在构造函数中进行初始化和设置。
分析:Child.prototype=new Father();产生Father的一个实例,同时赋值给Child的原型。也即:Child.prototype相当于对象{width:10,data:[1,2,3],key:"this is Father",[[Prototype]]:Father.prototype},这样就把Father的原型通过Child.prototype.[[Prototype]]这个对象属性保存起来,构成了原型的链接,但这样Child产生的对象的构造函数发生了改变,因为在Child中没有constructor属性,只能从原型链找到Father.prototype,读出constructor:Father
var one = new Child(); console.log(one.constructor);//输出Father()
所以,我们还要人为设回Child本身
Child.prototype.constructor =Child; //现在Child的原型就变成了{width:10,data:[1,2,3],key:"this is Father",[[Prototype]]:Father.prototype,constructor:Child} var one=new Child(); console.log(one.constructor);//输出Child()
当我们接着使用one.data.push(4);语句时,直接改变了prototype的data数组(引用)。所以有,
var two=new Child(); console.log(two.data);//输出 [1,2,3,4]
如果我们不想要父类Father自定义的属性,我们可以考虑将其过滤掉
//新建一个空函数 function F(){} //把空函数的原型指向构造函数Father的原型 F.prototype=Father.prototype; //这个时候再通过new操作把Child.prototype的原型链指向F的原型 Child.prototype=new F(); //这个时候Child的原型变成了{[[Prototype]] : F.prototype} //这里F.prototype其实只是一个地址的引用 //但是由Child创建的实例其constructor指向了Father,所以这里要显示设置一下Child.prototype的constructor属性 Child.prototype.constructor=Child; //这个时候Child的原型变成了{constructor:Child,[[Prototype]]:F.prototype} //这样就实现了Child对Father的原型继承
总结:继承应该是继承方法而不是属性,为子类设置父类实例属性应该是通过在子类构造函数中调用父类构造函数进行初始化。
方法2:父类构造函数原型与子类相同,修改子类原型添加方法会修改父类
分析:Child.prototype=Father.prototype;相当于把Child的prototype指向了Father的prototype,这样只是继承了Father的prototype方法,Father中的自定义方法则不继承。所以,
Child.prototype.say = "this is Child";//这样也会改变Father的prototype var f=new Father(); console.log(f.say);//输出this is Child
方法3:避免了方法1、2中的缺点,但是Object.create为EcmaScript 5方法,需要注意兼容性
改进:
所有三种方法应该在子类构造函数中调用父类构造函数实现实例属性初始化
function Child(){ Father.call(this); }
用新创建的对象替代子类默认原型,设置Child.prototype.constructor=Child;保证一致性
第三种方法的polyfill:
function create(obj){ if(Object.create){ return Object.create(obj); } function f(){}; f.prototype=obj; return new f(); }