1. 原型繼承
原型繼承是比較常見一種繼承方式
function Parent() {
this.name = 'parent'
this.action = function () {
console.log(this.name)
return this.name
},
this.arr = [1,2]
}
Parent.prototype.getName = function () {
console.log(this.name)
return this.name
}
function Child() {
this.name = 'child'
}
// 實例化父函數(shù)抡蛙,鏈接到自己的原型鏈上
Child.prototype = new Parent
const child1 = new Child
console.log(child1)
從上圖中可以看到, 原型繼承的特點(diǎn):
把父類的公有(prototype)+私有屬性都繼承到了子類的原型上(公有屬性 prototype)
注意:原型繼承的缺點(diǎn)
const child2 = new Child
console.log(child1)
child2.arr.push(3)
如果有一個子類 child2 調(diào)用 父類的 arr 屬性并添加了內(nèi)容
此時來看 child1 和 child2
從上面的結(jié)果中發(fā)現(xiàn)轮锥,child2 修改了父類的屬性幽告,child1 的父類屬性也被修改了。這也很好理解鹊奖,因為它倆都繼承與同一個 parent。它們的內(nèi)存空間是共享的。
所以:
原型的缺點(diǎn):子類會共享父類的的內(nèi)存空間
構(gòu)造函數(shù)繼承(call或apply)
function Parent() {
this.name = 'parent'
this.action = function () {
console.log(this.name)
return this.name
},
this.arr = [1,2]
}
Parent.prototype.getName = function () {
console.log(this.name)
return this.name
}
function Child () {
// 還有這里注意液走,如果子類的屬性名和父類的一樣,要看它的循序贾陷,誰在下面用誰的聲明
this.name = 'Child'
// Parent.call(this)
Parent.apply(this)
}
const child1 = new Child
const child2 = new Child
child2.arr.push(3)
console.log('child1 --->', child1)
console.log('child2 --->', child2)
使用這種解決了數(shù)據(jù)共享的問題
但是:
只能繼承父類的實例屬性和方法缘眶,不能繼承原型屬性或者方法。
組合繼承 (call + prototype)
function Parent() {
this.name = 'parent'
this.action = function () {
console.log(this.name)
return this.name
},
this.arr = [1,2]
}
Parent.prototype.getName = function () {
console.log(this.name)
return this.name
}
function Child() {
this.name = 'child'
Parent.call(this)
}
Child.prototype = new Parent
Child.prototype.constructor = Child
const child1 = new Child
console.log(child1)
優(yōu)點(diǎn):解決了 原型繼承和構(gòu)造函數(shù)繼承的缺點(diǎn)髓废。
但是這種方式也有缺陷:從上圖中可以看出巷懈,子類原型上多了一份父類的實例屬性。因為
父類的構(gòu)造函數(shù)被調(diào)用了兩次慌洪,生成了兩份顶燕,子類實例上的屏蔽了原型上的,造成了內(nèi)存浪費(fèi)
寄生式繼承 (Object.create)
就是使用原型繼承獲取一份淺拷貝對象冈爹。然后利用這個淺拷貝對象在進(jìn)行增強(qiáng)涌攻。
缺點(diǎn)和原型繼承一樣,但對于普通對象的繼承來說频伤,可以在父類的基礎(chǔ)上添加更多的方法
let Parent1 = {
arr: ['a', 'b'],
name: 'Parent1',
getName: function() {
return this.name
}
}
function Child(parent) {
let copy = Object.create(parent)
console.log(copy)
copy.getArray = function () {
return this.arr
}
return copy
}
let Child1 = Child(Parent1)
console.log(Child(Parent1))
console.log('child1 -->', Child1.getArray()) // ['a', 'b']
console.log('child1 -->', Child1.getName()) // Parent1
- 缺點(diǎn) 不能實例化恳谎,也沒有用到原型。
寄生組合式繼承
function Parent() {
this.name = 'parent'
this.action = function () {
return this.name
},
this.arr = [1,2]
}
Parent.prototype.getName = function () {
return this.name
}
function Child() {
Parent.call(this)
}
Child.prototype = Object.create(Parent.prototype)
Child.prototype.constructor = Child
const child1 = new Child
const child2 = new Child
console.log(child1)
- 解決了上面幾種幾種的缺陷剂买,也較好的實現(xiàn)了繼承的結(jié)果即
父類私有屬性放放子類私有中惠爽,原型上的屬性和方法放到子類原型上
注意:上面寫的繼承中癌蓖,為什么要手動修改 constructor 的指向
因為:在 JS 的規(guī)定中,xxx.prototype.constructor 的指向是當(dāng)前函數(shù)(類)本身婚肆。也就是指向自己
租副,我們上面用到的寄生繼承、組合繼承等较性,如果不修改 constructor 的指向用僧,它會指向 Parent 這個類, 如下:
// Child.prototype.constructor = Child
console.log('constructor -->', Child.prototype.constructor === Parent) // true
去掉手動指向,Child 的 constructor 就指向了 Parent赞咙,所以違背了 JS 的標(biāo)準(zhǔn)規(guī)范责循。
ES6 extends 關(guān)鍵字
在 ES6 中,直接使用 extends 關(guān)鍵字可以很容易的實現(xiàn) JavaScript 繼承攀操,并且 babel 編輯之后院仿,它采用的也是 寄生組合繼承的方式
,這種方式是較優(yōu)的解決繼承的方式速和。
class Parent2 {
constructor(name) {
this.name = name
}
getName = function (){
return this.name
}
}
class Child2 extends Parent2 {
constructor(name, age) {
super(name)
this.age = age
}
}
const child3 = new Child2('parent', 22)
const child4 = new Child2('parent1', 25)
console.log(child3.getName())
console.log(child3)
console.log(child4)