前言
看懂這篇文章需要你至少了解原型鏈的原理。
直接舉例子,先定義一個Person,再定義一個Man:
function Person(name){
this.name = name;
this.species='person'
this.have = [1,2,3,4]
}
Person.prototype.hello = () => {
console.log('person')
}
function Man(name) {
this.name = name;
this.gender='man'
}
Man.prototype.hello = () => {
console.log('man')
}
原型繼承
我們首先創建a,b兩個實例:
let a = new Person('a')
Person {name: "a", species: "person"}
name: "a"
species: "person"
have: (4) [1, 2, 3, 4]
__proto__:
hello: () => {console.log('person')}
constructor: ƒ Person(name)
__proto__: Object
let b = new Man('b')
Man {name: "b", gender: "man"}
name: "b"
gender: "man"
__proto__:
hello: () => {console.log('man')}
constructor: ƒ Man(name)
__proto__: Object
這時我們按照原型繼承的方式:
Man.prototype=a
Man.prototype.constructor=Man
然後我們再創建一個c實例,爲c的have方法推一個新值:
let c = new Man('c')
c.have.push(5)
Man {name: "c", gender: "man"}
name: "c"
gender: "man"
__proto__: Person
name: "a"
species: "person"
have: (5) [1, 2, 3, 4,5]
constructor: ƒ Man(name)
__proto__: Object
hello: () => { console.log('person') }
constructor: ƒ Person(name)
__proto__: Object
總結一下原型繼承的缺點:
- 子類原型上的方法被覆蓋。
- 父類引用類型的屬性會被子類公用。
- 子類型無法給父類型傳遞參數
類式繼承
針對上面的原型繼承的問題,我們可以採用類式繼承解決:
function Women(name){
Person.call(this, name)
this.gender='women'
}
let e = new Women('e')
name: "e"
species: "person"
have: (4) [1, 2, 3, 4]
gender: "women"
__proto__: Object
這樣就很好的解決了子類型向父類構造函數中傳參的問題,引用屬性也不會被公用。
但是這樣一來,其實問題又更多了,典型的就是沒辦法複用函數。同時Person原型定義的方法,再e中根本無法使用。
組合式繼承
很簡單,融合一下唄。
- 使用原型鏈對原型屬性和方法的繼承
- 構造函數對於實例屬性的繼承
function Child(name) {
Person.call(this, name)
this.gender='child'
}
Child.prototype.hello = () => {
console.log('child')
}
Child.prototype=new Person()
let f = new Child('f')
Child {name: "f", species: "person", have: Array(4), gender: "child"}
name: "f"
species: "person"
have: (4) [1, 2, 3, 4]
gender: "child"
__proto__: Person
name: undefined //不需要
species: "person" //不需要
have: (4) [1, 2, 3, 4] //不需要
__proto__:
hello: () => { console.log('person') }
constructor: ƒ Person(name)
__proto__: Object
同時我們再次push值給f.have的話:
f.have.push(5)
Child {name: "f", species: "person", have: Array(5), gender: "child"}
name: "f"
species: "person"
have: (5) [1, 2, 3, 4, 5]
gender: "child"
__proto__: Person
name: undefined
species: "person"
have: (4) [1, 2, 3, 4]
__proto__:
hello: () => { console.log('person') }
constructor: ƒ Person(name)
__proto__: Object
可以看到,haved的push出現在了f上,而它的原型上仍然是四個值的數組。
但是這種做法也是有點缺點的,典型的就是組合繼承使用過程中會父類構造函數被調用兩次。
而顯然,Child.prototype=new Person() 這一次是不需要的,因爲我們獲取實例屬性並不需要通過原型繼承。
這時你可能會想到:
Child.prototype = Person.prototype
的方法,這樣當然也是不對的,雖然可以避免去讀父類實例屬性,但是會把父類和字類的原型合併爲一個。
所以有了下面的方法。
組合寄生
這裏我們用了一部轉化:
function create(proto) {
function F(){};
F.prototype=proto;
return new F()
}
function Nobody(name) {
Person.call(this, name)
this.gender='nobody'
}
Nobody.prototype=create(Person.prototype)
Nobody.prototype.hello=()=>{console.log('nobody')}
Nobody.prototype.constructor=Nobody
和原來的組合繼承相比僅僅改了一處:
Nobody.prototype=create(Person.prototype)
Child.prototype =new Person()
得到的結果就相差極大:
let c = new Nobody('c')
Nobody {name: "c", species: "person", have: Array(4), gender: "nobody"}
name: "c"
species: "person"
have: (4) [1, 2, 3, 4]
gender: "nobody"
__proto__: Person
hello: ()=>{console.log('nobody')}
constructor: ƒ Nobody(name)
__proto__:
hello: () => { console.log('person') }
constructor: ƒ Person(name)
__proto__: Object
能做到這樣主要原因就是我們寫的這個create函數:
function create(proto) {
function F(){};
F.prototype=proto;
return new F()
}
這個函數中我們創建了一個空的構造函數,把他的prototype屬性指向傳入的參數,最後返回一個該構造函數的實例。這樣可以保證原型鏈的完整性,同時又可以保證實例的原型上沒有多餘屬性。
最後ES5中對這個create其實封裝好了,我們使用時如下即可:
function Nobody(name) {
Person.call(this, name)
this.gender='nobody'
}
Nobody.prototype=Object.create(Person.prototype)
Nobody.prototype.hello=()=>{console.log('nobody')}
Nobody.prototype.constructor=Nobody
創建個實例即:
let h = new Nobody('h')
Nobody {name: "h", species: "person", have: Array(4), gender: "nobody"}
name: "h"
species: "person"
have: (4) [1, 2, 3, 4]
gender: "nobody"
__proto__: Person
hello: ()=>{console.log('nobody')}
constructor: ƒ Nobody(name)
__proto__:
hello: () => { console.log('person') }
constructor: ƒ Person(name)
__proto__: Object
ES6的class語法糖
這就不講了,這還講個啥