彻底理解js面向对象之继承
说道这个继承,了解object-oriented的朋友都知道,大多oo语言都有两种,一种是接口继承(只继承方法签名);一种是实现继承(继承实际的方法)
奈何js中没有签名,因而只有实现继承,而且靠的是原型链实现的。下面正式的说一说js中继承那点事儿
1、原型链
原型链:实现继承的主要方法,利用原型让一个引用类型继承另一个引用类型的属性和方法。
回顾:构造函数,原型,实例三者的关系
每一个构造函数都有一个原型对象(person.prototype);原型对象都包含指向构造函数的指针(constructor);每个实例都包含指向原型对象的指针(看不见的_proto_指针)
原型链是怎么来的呢?
某个构造函数的原型对象是另一个构造函数的实例;这个构造函数的原型对象就会有个(看不见的_proto_指针)指向另一个构造函数的原型对象;
那么另一个原型对象又是其他的构造函数实例又会怎么样,就这样层层递进,形成原型链;来具体看一下吧
//第一个构造函数;有一个属性和一个原型方法 function supertype(){ this.property=true; } supertype.prototype.getsupervalue=function(){ return this.property } //第二个构造函数;目前有一个属性 function subtype(){ this.subproperty=false } //继承了supertype;subtype原型成了supertype的实例;实际就是重写subtype的原型对象;给supertype原型对象继承了 subtype.prototype=new supertype() //现在这个构造函数有两个属性(一个本身的subproperty,一个继承的存在原型对象的property);两个方法(一个原型对象的getsubvalue,一个原型对象的原型对象的getsupervalue) subtype.prototype.getsubvalue=function(){ return this.subproperty } var instance=new subtype() //创建第二个构造函数的实例 console.log(instance.getsupervalue()) //true 先查找instance这个实例有没有此方法;显然没有,再查找subtype原型对象有没有此方法;也没有,再查找subtype原型对象的原型对象;显然是存在的
注意: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()方法: 只要是原型链中出现过的原型,都可以说是该原型链所派生的实例的原型
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))
1.3谨慎定义方法
注意:给原型对象添加方法,一定放在替换原型的后面,因为放在替换原型之前是找不到了,原型会被重写的;
注意:在通过原型链继承时,不能使用对象字面量创建原型方法,因为也会重写原型链;
function supertype(){ this.property=true; } supertype.prototype.getsupervalue=function(){ return this.property } function subtype(){ this.subproperty=false } //继承supertype subtype.prototype=new supertype() //使用字面量添加新方法,导致上一行无效 因为现在的原型替换了object实例而非supertype的实例,关系中断 subtype.prototype={ getsubvalue:function(){ return this.subproperty; }, somothermethod:function(){ return false } }; var instance=new subtype() console.log(instance.getsupervalue()) //error
1.4原型链的问题
1、包含引用类型值的原型:当实例是另一函数的原型时,引用类型值就会变成原型上的属性,就会被另一函数的实例所共享。
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
2、创建子类型实例时,不能向超类型的构造函数传递参数(没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数)
2、借助构造函数
为了解决原型中包含引用类型值带来的问题,利用构造函数来解决
在子类型构造函数的内部调用超类型构造函数(函数是特定环境中执行代码的对象,可以通过apply或call调用)
function supertype(){ this.color=["yellow","red","olive"] } function subtype(){ //继承了supertype supertype.call(this) } var instance1=new subtype() instance1.color.push("purple") var instance2=new subtype() console.log(instance1.color) //["yellow","red","olive","purple"] console.log(instance2.color) //["yellow","red","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
问题:仅仅借鉴构造函数,那么避免不了构造函数的问题,方法都在构造函数定义了,函数无法复用
3、组合继承(常用的还是组合,和原型与构造结合一样)
function supertype(name){ this.name=name; this.color=["yellow","red","olive"]; } supertype.prototype.sayname=function(){ console.log(this.name); } function subtype(name,age){ //继承属性,创建属性副本 supertype.call(this,name); this.age=age; } //继承属性和方法,只是原型中属性被后来的函数调用生成的属性副本遮盖 subtype.prototype=new supertype(); alert(subtype.prototype.constructor) //指向的是supertype subtype.prototype.constructor=subtype; //将constructor回归到subtype构造函数身上 subtype.prototype.sayage=function(){ console.log(this.age) } var instance1=new subtype("double",23) instance1.color.push("pink") console.log(instance1.color) //["yellow","red","olive","pink"] instance1.sayname() //double instance1.sayage() //23 var instance2=new subtype("single",34) console.log(instance2.color) //["yellow","red","olive"] instance2.sayname() //single instance2.sayage() //34
还有其他的继承,花点时间写一下
1、原型式继承
克罗克福德写的;借助原型可以基于已有的对象创建新对象,同时不必创建自定义类型
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", "jack", "mike", "single"] var person2=object(person) person2.name="red" console.log(person2.friends) //["tom", "jack", "mike", "single"]
es5为了规范原型式的继承,有个object.create()来方便,ie9以上可以;只是想一个对象和另一个对象保持类似的情况,完全可以这种方法
var person={ name:"double", friends:["tom","jack","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()接受两个参数,一个为作为新对象原型的对象,一个为新对象定义额外属性对象 var person={ name:"double", friends:["tom","jack","mike"] } var person1=object.create(person,{ name:{ value:"single" //每个属性都是通过自己描述符定义的 } })
2、寄生式继承
思路和原型式继承一脉相承,创建一个用于封装继承过程的函数,内部通过方式增强对象,返回对象;主要考虑对象时使用
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 引用类型值还是共享的
3、寄生组合继承
组合继承是继承中常常用到的,但是会调用两次超类型构造函数;寄生组合继承就是为了解决这个问题的
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
到此,差不多结束啦,感谢你对的支持,希望我们整理的内容能够帮助到你。