js继承的几种方式

由浅入深的js继承的几种方式

1.借用构造函数继承

function Parent(){
    this.parent = 'parent'
}

function Son(){
    Parent.call(this)
    this.son = 'son'
}
let son = new Son()
son.parent //'parent'

这种方式是在构造函数Son中调用构造函数Parent,并且将Parent的this指针指向Son的实例对象,于是在执行Parent的时候this.parent就被添加在类Son的实例对象中,但是这种方法的缺点很明显没有将原型继承过来,算不上是完整的继承

2.原型链继承

function Parent1(){
    this.Parent1 = 'parent1'
    this.arr = [1,2,3]
}

function Son1(){
    this.son1 = 'son1'
}

Son1.prototype = new Parent1()
Parent1.prototype.say = function(){
    console.log('prototype inherit')
}

let son1 = new Son1()
son1.say() //'prototype inherit'
son1.Parent1 //'parent1'

原型链继承 让Son1的原型对象指向 Parent1的实例对象,那么Son1的实例对象就可以获取到Parent1的原型里的方法以及本身有的属性

son1.__proto__ === Son1.prototype // true

原因是因为对象在获取属性的时候,先找本身的属性有没有该属性,没有的话通过原型一级一级往上找,而son1的__proto__属性是和他的构造函数Son1的prototype指向同一个原型对象的,所以在调用son1.say()的时候,js发现son1里面没有say()这个属性于是向上寻找到__proto__即Parent1的的实例对象,然后继续找到Parent1的的实例对象的__proto__,而这个__proto__和Parent1的prototype指向同一个原型对象,于是在Parent1的prototype上添加的属性say就被找到并且调用了。

那这种方法的缺点如下

let s1 = new Son1()
let s2 = new Son2()
s1.arr // [1,2,3]
s1.arr.push(4)
s1.arr // [1,2,3,4]
s2.arr // [1,2,3,4]

arr属性是在Parent1的this对象赋值的也就是说是一个我们不希望被共享的属性,但是这种方法将这个属性也一并放到了原型中,所以我们在生成多个Son1的实例对象的时候,改变其中一个值另外一个也跟着改变了(基本类型值不会发生改变)

 

3.组合继承

function Parent2(){
    this.Parent2 = 'Parent2'
    this.arr = [1,2,3]
}
function Son2(){
    Parent2.call(this)
    this.son2 = 'son2'
}
Son2.prototype = new Parent2()

let s2 = new Son2()
let s3 = new Son2()
s2.arr // [1,2,3]
s3.arr // [1,2,3]
s2.arr.push(4)
s2.arr // [1,2,3,4]
s3.arr // [1,2,3]

这种继承将Parent2里面的this对象赋值的属性直接放到了Son2的实例的对象没有直接的放到原型中,所以在生成多个Son2实例对象的时候他们的arr是独立的,但这种方式的缺点是每一次new Son2的时候都会两次调用Parent2,浪费内存,打印s2或者s3可以发现他们的__proto__里面也有Parent2,arr属性(这也是原型链继承为什么能访问到这两个属性),于是组合继承可以进行第一次优化

4.组合继承的优化(1)

function Parent3(){
    this.Parent3 = 'Parent3'
    this.arr = [1,2,3]
}
function Son3(){
    Parent3.call(this)
    this.son2 = 'son3'
}
Son3.prototype = Parent3.prototype

将Son3的原型不指向Paretn3的实例对象而是直接指向其原型对象,这样就将原型对象的属性和this对象创造的属性分开继承,那么这种方式的缺点如下

let s4 = new Son3()
s4.constructor.name // 'Parent3'

因为s4.constructor其实是s4.__proto__.constructor  即Son3.prototype.constructor 而我们可以看到Son3.prototype.指向了Parent3.prototype所以打印出来的是 'Parent3'(当然上面组合继承和原型链继承也有这种问题),所以我们继续优化

5.组合继承的优化(2)

function Parent4(){
    this.Parent4 = 'Parent4'
    this.arr = [1,2,3]
}
function Son4(){
    Parent4.call(this)
    this.son4 = 'son4'
}
Son4.prototype = Object.create(Parent4.prototype)
Son4.prototype.constructor = Son4
let s7 = new Son4()
let P = new Parent4()
s7.constructor.name // Son4
P.constructor.name // Parent4

我们使用Object.create的方法将Son4的原型指向Parent4的原型,然后将Son4的prototype.constructor手动的改成Son4这样就可以了,Object.create这里也可以写成这样

let middleObj = {}
middleObj.__proto__ = Parent4.prototype
Son4.prototype = middleObj.__proto__
Son4.prototype.constructor = Son4

这样的继承就基本没什么毛病了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值