微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

彻底理解js面向对象之继承

说道这个继承,了解object-oriented的朋友都知道,大多oo语言都有两种,一种是接口继承(只继承方法签名);一种是实现继承(继承实际的方法

奈何js中没有签名,因而只有实现继承,而且靠的是原型链实现的。下面正式的说一说js中继承那点事儿

1、原型链

原型链:实现继承的主要方法,利用原型让一个引用类型继承另一个引用类型的属性方法

回顾:构造函数,原型,实例三者的关系

一个构造函数都有一个原型对象(Person.prototype);原型对象都包含指向构造函数的指针(constructor);每个实例都包含指向原型对象的指针(看不见的_proto_指针)

原型链是怎么来的呢?

某个构造函数的原型对象是另一个构造函数的实例;这个构造函数的原型对象就会有个(看不见的_proto_指针)指向另一个构造函数的原型对象;

那么另一个原型对象又是其他的构造函数实例又会怎么样,就这样层层递进,形成原型链;来具体看一下吧

rush:js;"> //第一个构造函数;有一个属性一个原型方法 function SuperType(){ this.property=true; }
SuperType.prototype.getSuperValue=function(){
    return this.property
}


//第二个构造<a href="https://www.jb51.cc/tag/hanshu/" target="_blank" class="keywords">函数</a>;目前有<a href="https://www.jb51.cc/tag/yige/" target="_blank" class="keywords">一个</a><a href="https://www.jb51.cc/tag/shuxing/" target="_blank" class="keywords">属性</a>
function SubType(){
    this.subproperty=false
}

//继承了SuperType;SubType原型成了SuperType的实例;实际就是重写SubType的原型对象;给SuperType原型对象继承了
SubType.prototype=new SuperType()

//现<a href="https://www.jb51.cc/tag/zaizhe/" target="_blank" class="keywords">在这</a>个构造<a href="https://www.jb51.cc/tag/hanshu/" target="_blank" class="keywords">函数</a>有两个<a href="https://www.jb51.cc/tag/shuxing/" target="_blank" class="keywords">属性</a>(<a href="https://www.jb51.cc/tag/yige/" target="_blank" class="keywords">一个</a>本身的subproperty,<a href="https://www.jb51.cc/tag/yige/" target="_blank" class="keywords">一个</a>继承的存在原型对象的property);两个<a href="https://www.jb51.cc/tag/fangfa/" target="_blank" class="keywords">方法</a>(<a href="https://www.jb51.cc/tag/yige/" target="_blank" class="keywords">一个</a>原型对象的getSubValue,<a href="https://www.jb51.cc/tag/yige/" target="_blank" class="keywords">一个</a>原型对象的原型对象的getSuperValue)
SubType.prototype.getSubValue=function(){
    return this.subproperty
}

var instance=new SubType()  //创建第二个构造<a href="https://www.jb51.cc/tag/hanshu/" target="_blank" class="keywords">函数</a>的实例

console.log(instance.getSuperValue())  //true 先查找instance这个实例有没有此<a href="https://www.jb51.cc/tag/fangfa/" target="_blank" class="keywords">方法</a>;显然没有,再查找SubType原型对象有没有此<a href="https://www.jb51.cc/tag/fangfa/" target="_blank" class="keywords">方法</a>;也没有,再查找SubType原型对象的原型对象;显然是存在的</pre>

注意:instance的constructor现在指向的是SuperType这个构造函数;因为原来的SubType.prototype被重写了,其内部的constructor也就随着SubType.prototype的原型对象的constructor指向构造函数SuperType;至于原型搜索机制是怎么样运行的,请仔细看上面的代码,相信你是可以的

1.1完整的原型

在原型那节已经提了些,还是再说一下。完整的原型包括Object。

所有函数认原型都是Object的实例;每个认原型都有个_proto_指针指向Object.prototype;因此自定义类型都继承如toString,valueOf的方法

而Object.prototype的_proto_指针指向null来结束原型链。以Person构造函数为例,看看完整的原型链图

1.2原型和实例的关系判断

第一种使用instanceof操作符: 测试实例和原型链中出现的构造函数,结果为true

第二种使用isPrototypeOf()方法: 只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型

rush:js;"> console.log(instance instanceof Object) //都为true console.log(instance instanceof SuperType) console.log(instance instanceof SubType)
console.log(Object.prototype.isPrototypeOf(instance)) //都为true
console.log(SuperType.prototype.isPrototypeOf(instance))
console.log(SubType.prototype.isPrototypeOf(instance))</pre>

1.3谨慎定义方法

注意:给原型对象添加方法,一定放在替换原型的后面,因为放在替换原型之前是找不到了,原型会被重写的;

注意:在通过原型链继承时,不能使用对象字面量创建原型方法,因为也会重写原型链;

rush:js;"> function SuperType(){ this.property=true; }
SuperType.prototype.getSuperValue=function(){
    return this.property
}

function SubType(){
    this.subproperty=false
}

//继承SuperType
SubType.prototype=new SuperType()

//使用字面量<a href="https://www.jb51.cc/tag/tianjia/" target="_blank" class="keywords">添加</a>新<a href="https://www.jb51.cc/tag/fangfa/" target="_blank" class="keywords">方法</a>,导致上一行无效   因为现在的原型替换了Object实例而非SuperType的实例,关系中断
SubType.prototype={
   getSubValue:function(){
       return this.subproperty;
   },somOtherMethod:function(){
       return false
   }
};

var instance=new SubType()
console.log(instance.getSuperValue())  //error</pre>

1.4原型链的问题

1、包含引用类型值的原型:当实例是另一函数的原型时,引用类型值就会变成原型上的属性,就会被另一函数的实例所共享。

rush:js;"> function SuperType(){ this.colors=["yellow","red","olive"] }
function SubType(){
}

SubType.prototype=new SuperType()  //color实际上就是原型上的了

var instance1=new SubType()
instance1.colors.push("purple")
var instance2=new SubType()

console.log(instance1.colors==instance2.colors)  //true</pre>

2、创建子类型实例时,不能向超类型的构造函数传递参数(没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数)

2、借助构造函数

为了解决原型中包含引用类型值带来的问题,利用构造函数解决

在子类型构造函数的内部调用超类型构造函数函数是特定环境中执行代码的对象,可以通过apply或call调用

rush:js;"> function SuperType(){ this.color=["yellow","olive"] }
function SubType(){
    //继承了SuperType
    SuperType.call(this)
}

var instance1=new SubType()
instance1.color.push("purple")
var instance2=new SubType()

console.log(instance1.color)  //["yellow","olive","purple"]
console.log(instance2.color)  //["yellow","olive"]


//传递参数
function SuperType(name){
   this.name=name
}
function SubType(){
    SuperType.call(this,"double")
    this.age=12
}

var instance1=new SubType()
console.log(instance1.name)  //double
console.log(instance1.age)  //12</pre>

问题:仅仅借鉴构造函数,那么避免不了构造函数的问题,方法都在构造函数定义了,函数无法复用

3、组合继承(常用的还是组合,和原型与构造结合一样)

rush:js;"> function SuperType(name){ this.name=name; this.color=["yellow","olive"]; }
SuperType.prototype.sayName=function(){
    console.log(this.name);
}

function SubType(name,age){
    //继承<a href="https://www.jb51.cc/tag/shuxing/" target="_blank" class="keywords">属性</a>,创建<a href="https://www.jb51.cc/tag/shuxing/" target="_blank" class="keywords">属性</a>副本
    SuperType.call(this,name);
    this.age=age;
}

//继承<a href="https://www.jb51.cc/tag/shuxing/" target="_blank" class="keywords">属性</a>和<a href="https://www.jb51.cc/tag/fangfa/" target="_blank" class="keywords">方法</a>,只是原型中<a href="https://www.jb51.cc/tag/shuxing/" target="_blank" class="keywords">属性</a>被后来的<a href="https://www.jb51.cc/tag/hanshu/" target="_blank" class="keywords">函数</a><a href="https://www.jb51.cc/tag/diaoyong/" target="_blank" class="keywords">调用</a><a href="https://www.jb51.cc/tag/shengcheng/" target="_blank" class="keywords">生成</a>的<a href="https://www.jb51.cc/tag/shuxing/" target="_blank" class="keywords">属性</a>副本遮盖
SubType.prototype=new SuperType();

alert(SubType.prototype.constructor)  //指向的是SuperType

SubType.prototype.constructor=SubType; //将constructor回归到SubType构造<a href="https://www.jb51.cc/tag/hanshu/" target="_blank" class="keywords">函数</a>身上
SubType.prototype.sayAge=function(){
    console.log(this.age)
}


var instance1=new SubType("double",23)
instance1.color.push("pink")
console.log(instance1.color)     //["yellow","pink"]
instance1.sayName()         //double
instance1.sayAge()          //23

var instance2=new SubType("single",34)
console.log(instance2.color)     //["yellow","olive"]
instance2.sayName()         //single
instance2.sayAge()          //34</pre>

还有其他的继承,花点时间写一下

1、原型式继承

克罗克福德写的;借助原型可以基于已有的对象创建新对象,同时不必创建自定义类型

rush:js;"> function object(o){ //本质上object()函数对其中对象的浅复制 function F(){} //创建一个新的构造函数 F.prototype=o //构造函数原型为传入的对象 return new F() //返回构造函数的实例 }
var person={
    name:"double",friends:["tom","jack","mike"]
}

var person1=object(person)   //事实上为原型共享
person1.name="grey"
person1.friends.push("single")

console.log(person1.friends)  //["tom","mike","single"]

var person2=object(person)
person2.name="red"
console.log(person2.friends)   //["tom","single"]</pre>

ES5为了规范原型式的继承,有个Object.create()来方便,IE9以上可以;只是想一个对象和另一个对象保持类似的情况,完全可以这种方法

rush:js;"> var person={ name:"double","mike"] }
var person1=Object.create(person)
person1.name="single"
person1.friends.push("singles")

var person2=Object.create(person)

console.log(person1.friends==person2.friends) //true

//Object.create()接受两个参数,<a href="https://www.jb51.cc/tag/yige/" target="_blank" class="keywords">一个</a>为作为新对象原型的对象,<a href="https://www.jb51.cc/tag/yige/" target="_blank" class="keywords">一个</a>为新对象定义额外<a href="https://www.jb51.cc/tag/shuxing/" target="_blank" class="keywords">属性</a>对象
var person={
    name:"double","mike"]
}

var person1=Object.create(person,{
     name:{ 
        value:"single"  //每个<a href="https://www.jb51.cc/tag/shuxing/" target="_blank" class="keywords">属性</a>都是通过自己描述符定义的
     }
})</pre>

2、寄生式继承

思路和原型式继承一脉相承,创建一个用于封装继承过程的函数,内部通过方式增强对象,返回对象;主要考虑对象时使用

rush:js;"> function object(o){ function F(){} F.prototype=o return new F() }
function createPerson(original){
   var clone=object(original)   //继承原型
   clone.sayName=function(){ 
       alert("name")
   }
   return clone
}

var person={
   name:"double",friends:["single","tom","jack"]
}

var person1=createPerson(person)
person1.sayName()  //name   引用类型值还是共享的</pre>

3、寄生组合继承

组合继承是继承中常常用到的,但是会调用两次超类型构造函数;寄生组合继承就是为了解决这个问题的

rush:js;"> function object(o){ function F(){} F.prototype=o return new F() }

function inheritPrototype(subType,superType){
var prototype=object(superType) //创建对象 (superType实例)
prototype.constructor=subType //增强对象
subType.prototype=prototype //指定对象 (原型赋予实例)
}

function SuperType(name,sex){
this.name=name
this.sex=sex
this.colors=["red"]
}

SuperType.prototype.sayName=function(){
alert(this.name)
}

function SubType(name,sex,age){
SuperType.call(this,name,sex)
this.age=age
}

inheritPrototype(SubType,SuperType) //目前subType.prototype什么都没有
SubType.prototype.sayAge=function(){ //为subType.prototype添加方法
alert(this.age)
}

var person1=new SubType("double","man",34)
console.log(person1.name) //SuperType 这是个Bug
console.log(person1.sex) //man
console.log(person1.colors) //["red"]
person1.sayAge() //34

到此,差不多结束啦,感谢你对编程之家的支持,希望我们整理的内容能够帮助到你。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐