1.继承目的
最根本的目的应该是让子类能“拥有”父类及其所有祖先类的属性和方法,实现方式有两种
- 第一种,通过把所有祖先类的属性和方法复制到子类中
- 第二种,通过原型直接访问祖先类中的属性和方法
2.继承演进
//父类
function Parent(){
this.name="super";
this.eat=function(){
console.log("super eat");
}
//构造函数体
console.log("super constructed");
}
- prototype 方式
//子类
function Child(){
}
//child原型属性赋值父对象
Child.prototype=new Parent();
console.log(child.name)//访问parent对象的name属性
child.name="child"//此处有一个问题,这里的name并不是parent的name属性,实际上我们为child对象创建了一个name属性,并为其赋值,所以我们无法通过原型去修改name属性或者方法。由于没能复用parent属性,不能算是继承。
- 通过apply或call方法
//子类
function Child(){
//这相当于把Parent中的属性和方法复制到Child中,并执行Parent构造函数体。
//存在的一个问题是没办法复制Parent原型属性
Parent.call(this);
}
- 结合1,2两种写法
//子类
function Child(){
Parent.call(this);//复制Parent属性和方法,并执行Parent构造函数
}
//让child可以访问Parent原型中的方法和属性,并执行Parent构造函数
Child.prototype=new Parent();
这种方式确实达到了目的,让子类拥有了Parent的属性和方法,和访问Parent prototype 的能力。但仍存在一个问题,执行了两次Parent构造函数。
- 3写法的优化
//子类
function Child(){
Parent.call(this);//复制Parent属性和方法,并执行Parent构造函数
}
//我们只需要Parent的prototype并不需要再次调用Parent构造函数
var prototypeObj= function(){};//存储当前当前类prototype属性值
prototypeObj.prototype = Parent.prototype;
Child.prototype = new prototypeObj();
var child=new Child();
//eat方法查找流程如下
//先查找Child对象,如果没有,则查找其prototype指向的对象,prototype指向一个prototypeObj()对象,所以查找prototypeObj,如果没有再去查找prototypeObj的prototype指向的对象,此时prototypeObj.prototype指向的是Parent.prototype,和Child一样,Parent.prototype同样指向了一个prototypeObj()对象,如此循环往复的沿着prototype链查找eat方法。
child.eat();
3.总结
- 可以把一个function 分为两类数据,一类this指向的方法和属性,如Parent中的this.name和this.eat,另一类则是prototype所指向的方法和属性,继承的目的是要让子类拥有访问父类及其祖先类访问这两种数据的能力。
- 第一类数据可以通过apply和call来实现,第二类数据通过类的prototype的传递性和一个用于存储当前类prototype属性值的临时对象prototypeObj来实现
- 最终形成一个查找链{child}|{parent}|{…}->{child.prototype=new prototypeObj()}->{parent.prototype=new prototypeObj()}->{…prototype=new prototypeObj()}