在我近三年的職業(yè)生涯中铐拐,老實(shí)說(shuō)徘键,我很少需要自己去寫一個(gè)類,然后繼承遍蟋,然后如何如何去調(diào)用這些方法吹害,除了有一次公司緊急任務(wù)需要用canvas寫一個(gè)打飛機(jī)的游戲之外,大多數(shù)時(shí)間都是用現(xiàn)有的工具虚青,庫(kù)它呀,框架就能完成任務(wù)了。但是呢棒厘,寫久了就會(huì)厭倦纵穿,而想要進(jìn)階高級(jí)前端,面向?qū)ο缶幊躺萑耍瑥氐桌斫庠臀矫健㈤]包這些都是必須的。所以何乎,砥礪前行吧句惯。今天就來(lái)聊一聊js繼承的幾種方式。
一支救,原型鏈繼承
function Fu(){
this.name = 'Fu'
this.sayName = function(){
console.log(this.name)
}
}
function Zi(name) {
this.name = name
}
Zi.prototype = new Fu()
例子1的父類有默認(rèn)的name='Fu'抢野,和一個(gè)sayName()方法,子類需要每個(gè)實(shí)例都有自己的name,如下圖各墨,每個(gè)實(shí)例都有自己的name蒙保,并且可以調(diào)用父類繼承的sayName()方法
雖然上面的方法實(shí)現(xiàn)了繼承,但是也有問(wèn)題的欲主。
問(wèn)題一:切斷了 Zi.prototype.constructor與Zi的關(guān)系
Fu.prototype.constructor === Fu // true
這個(gè)沒(méi)問(wèn)題邓厕,
但是
Zi.prototype.constructor === Zi // false
這就不對(duì)了,按理來(lái)說(shuō) Zi.prototype.constructor === Zi //true才是對(duì)的扁瓢,為什么會(huì)這樣呢详恼?成也蕭何敗也蕭何,問(wèn)題出在了 Zi.prototype = new Fu()這一句雖然實(shí)現(xiàn)了繼承引几,但是也切斷了 Zi.prototype.constructor與Zi的關(guān)系昧互。相當(dāng)于給 Zi.prototype重新賦值了挽铁,所以要正確無(wú)誤的繼承,在最后我們還要加上這句代碼:
Zi.prototype.constructor = Zi // 重新把他們的關(guān)系建立起來(lái)敞掘。
第二個(gè)問(wèn)題:原型鏈上的引用類型的數(shù)據(jù)會(huì)被所有實(shí)例共享
看下面例子:
1叽掘、 Fu中定義了一個(gè)引用類型的變量,
2玖雁、 Zi類實(shí)例z1 z2都能訪問(wèn)到更扁,結(jié)果都是['1', '2', '3']
3、z1修改了這個(gè)引用類型的變量赫冬,z2沒(méi)修改
4浓镜、結(jié)果是z2也被影響到了
所以,實(shí)際開(kāi)發(fā)一般不會(huì)單獨(dú)用原型鏈來(lái)做劲厌。
二膛薛,借用構(gòu)造函數(shù)繼承
function Fu(name){
this.name = name
this.sayName = function(){
console.log(this.name)
}
}
Fu.prototype.sayHi = function (){
console.log('hi')
}
function Zi() {
Fu.apply(this, arguments)
}
let z = new Zi('兒子')
console.log(z)
利用apply函數(shù)將父類的構(gòu)造函數(shù)直接在子類中運(yùn)行,這樣就等于是把父類的語(yǔ)句搬到子類中补鼻,并且把this轉(zhuǎn)移到子類了哄啄。如下圖例子1, 實(shí)例z 繼承了父類的sayName()方法,但是也有缺點(diǎn):
1风范、Fu.prototype上的方法無(wú)法繼承
2咨跌、Fu構(gòu)造函數(shù)中定義的方法無(wú)法復(fù)用
三、組合繼承
說(shuō)白就是把前面兩種組合起來(lái)
function Fu(){
this.name = 'Fu'
}
Fu.prototype.sayName = function (){
console.log(this.name)
}
function Zi(name) {
Fu.apply(this, arguments)
this.name = name
}
Zi.prototype = new Fu()
Zi.prototype.constructor = Zi
let z = new Zi('兒子')
優(yōu)點(diǎn):解決上面的問(wèn)題
缺點(diǎn):調(diào)用兩次Fu的構(gòu)造函數(shù)
四乌企、寄生組合式繼承
function Fu(){
this.name = 'Fu'
}
Fu.prototype.sayName = function (){
console.log(this.name)
}
function Zi(name) {
Fu.apply(this, arguments)
this.name = name
}
Zi.prototype = Obeject.create(Fu.prototype)
Zi.prototype.constructor = Zi
let z = new Zi('兒子')
基本到這里就沒(méi)啥問(wèn)題了虑润,Obeject.create(Fu.prototype)這句就是對(duì)Fu.prototype的淺拷貝,而不是調(diào)用Fu的構(gòu)造函數(shù)。
// Obeject.create(Fu.prototype)相當(dāng)于下面的函數(shù)
function create(obj) {
let F = function(){}
F.prototype = obj
return new F()
}
create(Fu.prototype)
五、class extends
class和extends是在ES6中新增的苇本,class用來(lái)創(chuàng)建一個(gè)類暇仲,extends用來(lái)實(shí)現(xiàn)繼承
class Fu{
constructor(name){
this.name = name
}
sayName(){
console.log(this.name)
}
}
class Zi extends Fu{
constructor(name){
super(name) // 這句很重要
this.name = name
}
}
注意:子類必須在constructor方法中調(diào)用super方法,否則新建實(shí)例時(shí)會(huì)報(bào)錯(cuò)。這是因?yàn)樽宇悰](méi)有自己的this對(duì)象,而是繼承父類的this對(duì)象,然后對(duì)其進(jìn)行加工亚亲。如果不調(diào)用super方法,子類就得不到this對(duì)象腐缤。