由浅入深的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
这样的继承就基本没什么毛病了