彻底理解继承

简单回顾原型链

原型链

我们每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针constructor,而实例都包含一个指向原型对象的内部指针proto。如果我们让原型对象等于另一个类型的实例,这时原型对象将包含指向另一个原型的指针,另一个原型中也包含指向另一个构造函数的指针。 假如另一个原型又是另一个类型的实例,那么上述关系成立,层层递进,构成了实例与原型链。

原型链继承

举例

function SuperType(){
    this.property=true;
}
Super.prototype.getSuperValue=function(){
    return this.property;
};

function SubType(){
    this.subProperty=false;
}
SubType.prototype.getSubValue=function(){
    return this.subProperty;
}
//
var instance=new SubType();

SubType继承了SuperType。
怎么继承的?
我们把SuperType的实例赋给SubType的prototype了。
实现的本质是重写原型对象
这里写图片描述
其实我们是没有使用SubType的默认提供的原型的!
而是换了一个原型,这个原型对象就是SuperType的。
instance–> SubType–>SuperType

你会发现property是SuperType的实例属性,这个属性存在于SubType的原型中!
其实这也印证了JS中只有对象,没有类的事实。

再看constructor

而instance的constructor现在指向了SuperType,因为instance是SubType的实例,而SubType的原型指向了另一个对象SuperType的原型,这个原型对象的constructor是SuperType,所以SubType的员阿里的默认constructor已经不复存在了!

默认原型

所有函数的默认原型都是Object的实例。
默认原型包含一个内部指针__proto__,指向Object.prototpe

确定原型和实例的关系

instanceof 操作符
由于原型链的关系,instance是Object SubType SuperType的任何一个类型的实例。
测试这三个构造函数都返回了true。

isPrototypeOf()
只要是原型链出现过的原型,都可以说是该原型链派生的实例的原型。都会返回true

instance instanceof Object ; //true
Object.prototype.isPrototypeOf(instance);//true

添加/定义方法

通过原型链,扩展了原型搜索机制。我们给原型添加方法的代码一定要放在替换原型的语句之后。

function SuperType(){
    this.property=true;
}
Super.prototype.getSuperValue=function(){
    return this.property;
};

function SubType(){
    this.subProperty=false;
}
//继承了SuperType
SubType.prototupe=new SuperType();
SubType.prototype.getSubValue=function(){
    return this.subProperty;
}
//
SuperType.prototype.getSuperValue=function(){
//重写了继承的方法
}
var instance=new SubType();

重写继承的方法会屏蔽原来的方法。因为原型搜索的缘故。
但是对于SuperType的实例来说,它还是调用本来的那个方法,因为它的原型并没有改变。

不能使用对象字面量创建原型方法,否则就会重写原型链。

原型链的问题

1. 第一个问题是,也是最主要的问题就是,包含引用类型值的原型属性会被共享。通过原型来实现继承时,原型实际上会变成另一个类型的实例。 于是原来的实例属性也就称为了现在的原型属性了。
例如:

function SuperType(){
    this.colors=['red','g','b'];
}

function SubType(){

}

//继承了SuperType
SubType.prototype=new SuperType();
var instance1=new SubType();
instance1.colors.push('blc');
//instance1.colors  ['red','g','b','blc']

定义这个colors属性,它是SuperType的实例属性,但是我们SubType.prototype=new SuperType();
就把SuperType的实例属性赋给SubType.prototype了,它的原型也就拥有了这个属性。
相当于:
创建了一个SubType.prototype.color属性!

2.第二个问题
创建子类型的实例时,不能向超类型的构造函数传递参数。 没有办法在不影响所有对象实例的情况下,给超类型构造函数传递参数。 因为这样就会导致子类型的原型也有这些属性了。

借用构造函数

在子类型构造函数内部调用超类型构造函数。

function SuperType(){
    this.colors=['red','g','b'];
}

function SubType(){
    //继承了SuperType
    SuperType.call(this);
}

我们借用了超类型的构造函数,通过使用call方法,实际上就是在SubType实例环境下调用了SuperType构造函数。
这样就可以在SubType的每个实例上拥有自己的colors属性的副本了。

借用构造函数的问题

同样还是构造函数模式的问题,函数无法复用!
在这种模式下,超类型的原型中定义的方法,对子类型而言也是不可见的。

组合继承

使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承

function SuperType(name){
    this.name=name;
    this.colors=['red','g','b'];
}
SuperType.prototype.sayName=function(){

}

function SubType(name,age){
    //继承了属性 ,SubType的实例可以有不同的实例属性
    SuperType.call(this,name);
    this.age=age;  //自己的属性
}
//继承方法
SubType.prototype=new SuperType();
//constructor指回SubType构造函数,不然就是SuperType的构函数,但是其实改变这个值没什么影响
SubType.prototype.construcotr=SubType;
SubType.prototype.sayAge=function(){
    alert(this.age);
}

SuperType的实例赋给SubType.prototype之后,使得SubType可以继承SuperType的方法(其实就是原型链能搜索到)。
然后SuperType的属性都会变成SubType的原型属性。 但是我们调用了 SuperType.call(this,name),导致SubType就可以有自己的实例属性了! 重新执行了一次这个构造函数。

原型式继承

借助原型可以基于已有的对象创建新对象,同时不必因此创建自定义类型。
道格拉斯:

function object(o){
    function F(){}
    F.prototype=o;
    return new F();
}

在这个object函数内部,有一个临时构造函数,然后把传入的对象赋给它的原型(就是把原型设为这个传入对象),最后返回了这个临时类型的一个新实例。

本质
执行了一次浅复制
我们传入的对象是我们的基础。 返回的新对象是将person作为原型,所以它的原型中就包含一个基本类型值属性和一个引用类型值属性。

var person={
    name:"NI",
    friends:[1,2,3]
}
var anotherPerson=object(person);
var yetPerson=object(person);

我们新创建的对象就会将person作为原型,然后它的原型中就包含一个基本类型值属性和一个引用类型值属性。
person.friends这个属性不仅在person有,anotherPerson yetPerson也会共享这个属性。
相当于创建了person对象的两个副本。

ES5 使用Object.create()
就等于这个方法。 传入两个参数,
一个用作新对象原型的对象 和 一个为新对象定义额外属性的对象~!

var anotherPerson=Object.create(person);
var yetPerson=Object.create(person);

缺点:
引用类型值的属性始终会共享相应的值。相同的值。和原型模式一样

寄生式继承

思路类似工厂模式/寄生构造模式。 所谓寄生即依赖一个对象
创建一个仅用于封装继承过程的函数
该函数内部以某种方式来增强对象。

function createAnother(original){
//创建了一个新对象
    var clone=object(original);
    //增强这个对象
    clone.sayHI=function(){};
//返回这个对象
    return clone;
}

对于寄生继承来说,主要考虑对象而不是自定义类型和构造函数的情况下,寄生式继承也是一种有用的模式。 我们不一定要使用object(),任何能够返回新对象的函数都适用这个模式!

问题:
寄生式继承,仍然无法做到函数复用。

寄生组合式继承

JS最常用的继承模式。
组合继承的问题:无论什么情况下都会调用两个超类型构造函数。
看我们前面的代码就可发现:
SuperType.call(this,name);//second
SubType.prototype=new SuperType();//first
我们第一次调用new SuperType绑定原型给SubType的时候,我们的原型对象prototype就会得到这两个属性。
当我们第二次调用这个构造函数时,就在上面创建了实例属性name和colors。于是这两个属性就屏蔽了原型中的两个同名属性~!

我们两组属性,一组在实例,一组在SubType原型中。寄生组合:借用构造函数继承属性,原型链混成形式来继承方法。

关键:不必为了指定子类新的原型而调用超类型的构造函数,我们需要的只是超类型原型的一个副本~!

function inheritPrototype(subType,superType){
//创建对象,就是创建超类型的一个副本
    var prototype=object(superType.prototype);
    //为创建的副本添加constructor,因为重写原型会失去默认的这个constructor属性
    prototype.constructor=subType;
    //将新创建的对象(副本)赋值给子类新的原型。
    subType.prototype=prototype;
}

function SuperType(name){
    this.name=name;
    this.colors=['red','g','b'];
}
SuperType.prototype.sayName=function(){
    console.log(this.name);
};
function SubType(name,age){
    SuperType.call(this,name);
    this.age=age;
}

inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge=function(){

}

它的高效率体现在只调用了一次SuperType构造函数。避免了在SubType.prototype上创建不必要多余的属性

寄生组合继承是引用类型的最理想继承方式!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值