摘要:繼承是面向?qū)ο笏枷胫械闹匾拍睿m然嚴(yán)格來(lái)說(shuō) JavaScript 并屬于面向?qū)ο箢?lèi)型的語(yǔ)言换吧,但最終還是在ES 6 中正式引入了繼承的概念。本文就來(lái)介紹一下繼承的概念、以及在 ES5 和 ES6 中兩種寫(xiě)繼承的方法冒签。
面向?qū)ο笾械睦^承
話不多說(shuō),先來(lái)看一下維基百科的解釋
繼承( inheritance)是面向?qū)ο筌浖夹g(shù)當(dāng)中的一個(gè)概念钟病。如果一個(gè)類(lèi)別B“繼承自”另一個(gè)類(lèi)別A萧恕,就把這個(gè)B稱為“A的子類(lèi)”,而把A稱為“B的父類(lèi)別”也可以稱“A是B的超類(lèi)”肠阱。繼承可以使得子類(lèi)具有父類(lèi)別的各種屬性和方法票唆,而不需要再次編寫(xiě)相同的代碼。
所以我們知道了繼承是類(lèi)和類(lèi)之間的關(guān)系屹徘,它使得子類(lèi)具有了父類(lèi)別的各種屬性和方法走趋。所以繼承這個(gè)概念本身是屬于面向?qū)ο缶幊痰模挥忻嫦驅(qū)ο缶幊汤锩娌庞蓄?lèi)的概念噪伊。
然而 JavaScript 是函數(shù)式編程簿煌,根本就沒(méi)有類(lèi)的概念,那么 JS 中的繼承的概念從何談起呢鉴吹?
JavaScript 中的繼承
在說(shuō) JS 的繼承之前姨伟,我們得先了解原型鏈的概念,簡(jiǎn)單來(lái)說(shuō)就是每一個(gè)對(duì)象都有一個(gè)__proto__
屬性拙寡,指向該對(duì)象的原型 prototype
授滓,原型的層層指向就形成了一個(gè)類(lèi)似鏈表的結(jié)構(gòu)(實(shí)際上原型的指向是多對(duì)一的,所以把這種結(jié)構(gòu)說(shuō)成“樹(shù)”更準(zhǔn)確一點(diǎn))稱之為原型鏈。
JavaScript 對(duì)象是動(dòng)態(tài)的屬性“包”(指其自己的屬性)般堆。JavaScript 對(duì)象有一個(gè)指向一個(gè)原型對(duì)象的鏈在孝。當(dāng)試圖訪問(wèn)一個(gè)對(duì)象的屬性時(shí),它不僅僅在該對(duì)象上搜尋淮摔,還會(huì)搜尋該對(duì)象的原型私沮,以及該對(duì)象的原型的原型,依次層層向上搜索和橙,直到找到一個(gè)名字匹配的屬性或到達(dá)原型鏈的末尾仔燕。
而 JavaScript 中的繼承就是基于原型鏈的繼承讼昆。
由于JS里面沒(méi)有類(lèi)副瀑,只有函數(shù),所以我們只好這么稱呼:子類(lèi)函數(shù)眯牧、父類(lèi)函數(shù)(比如 Array
與 Object
的關(guān)系)办斑,因此可以這么解釋繼承:子類(lèi)函數(shù)構(gòu)造出來(lái)的對(duì)象直接擁有父類(lèi)的屬性外恕。
因此,我們要寫(xiě)出繼承乡翅,還得先寫(xiě)一個(gè)類(lèi)鳞疲,那么類(lèi)到底是什么呢:能產(chǎn)生對(duì)象的東西即為類(lèi),那么蠕蚜,下面的代碼中尚洽,Human
就可以稱之為類(lèi)。person
就是一個(gè)有自己屬性的對(duì)象靶累。
function Human(name){
this.name = name
}
var person = new Human('Enoch')
如果我們想在它的原型鏈上加一個(gè)屬性腺毫,就可以這么寫(xiě):
Human.prototype.run = function(){}
現(xiàn)在 person
的原型結(jié)構(gòu)就是這樣的:
有了類(lèi),下面就可以來(lái)寫(xiě)繼承了尺铣,我們要實(shí)現(xiàn)一個(gè)類(lèi) Man
繼承自 Human
拴曲,他有自己的屬性,同時(shí)也繼承了 Human
的屬性和方法凛忿。
ES 5 中的繼承寫(xiě)法
function Human(name){
// 自身的屬性
this.name = name
}
// 原型鏈第一層的屬性
Human.prototype.run = function(){
console.log("我叫"+this.name+"澈灼,我在跑")
return undefined
}
// 聲明一個(gè) Man 類(lèi)
function Man(name){
// 使得 Man 類(lèi)有了 Human 類(lèi) 第一層的 name 屬性
// 即實(shí)現(xiàn)了對(duì)象本身屬性的添加
Human.call(this, name)
this.gender = '男'
}
// 接下來(lái)要實(shí)現(xiàn) Man 也擁有 Human 第二層的方法 run
// 最簡(jiǎn)單的方式是:
// Man.prototype.__proto__ = Human.prototype
// 但是上一句不符合 ECMA 標(biāo)準(zhǔn),因此要寫(xiě)下面三行
var f = function(){}
f.prototype = Human.prototype
Man.prototype = new f()
// 此為 Man 類(lèi)自身具有的方法店溢,處于原型鏈第二層
Man.prototype.fight = function(){
console.log('糊你熊臉')
}
寫(xiě)好以后叁熔,聲明一個(gè) person
對(duì)象: var person = new Man('Enoch')
此時(shí) person
的結(jié)構(gòu)如下:
我們來(lái)理一下 person 對(duì)象的原型鏈
person.__proto__ === Man.prototype
person.__proto__.__proto__ === Human.prototype
person.__proto__.__proto__.__proto__ === Object.prototype
person.__proto__.__proto__.__proto__.__proto__ === null
ES 6 中的繼承寫(xiě)法
需要注意的是,ES 6 雖然新增了 class
語(yǔ)法床牧,但它本質(zhì)上只是一個(gè)語(yǔ)法糖而已荣回,JavaScript 目前為止仍然是基于原型鏈的繼承,沒(méi)有真正的類(lèi)的概念戈咳。
class Human{
constructor(name){
this.name = name
}
run(){
console.log("我叫"+this.name+"心软,我在跑")
return undefined
}
}
// extends 關(guān)鍵字 等價(jià)于
// Man.prototype.__proto__ = Human.prototype
class Man extends Human{
constructor(name){
// super(name) 等價(jià)于 Human.call(this,name)
super(name)
this.gender = '男'
}
fight(){
console.log('糊你熊臉')
}
}
ES 6 寫(xiě)法的不足
看了上一節(jié)壕吹,我們會(huì)發(fā)現(xiàn) ES 6 寫(xiě)起來(lái)好爽,代碼簡(jiǎn)潔了很多删铃,而且曾經(jīng)寫(xiě)過(guò) Java 的同學(xué)還會(huì)覺(jué)得分外親切耳贬,因?yàn)檫@種寫(xiě)法就是 JavaScript 借( chao )鑒( xi )了 Java 的類(lèi)的繼承的操作。但是這么寫(xiě)還有一個(gè)需求不太容易實(shí)現(xiàn)猎唁。
比如我們需要在類(lèi)的原型上寫(xiě)一個(gè)不是函數(shù)的屬性咒劲,比如 Human
的原型上有一個(gè) 種族 race
的屬性, 就不能直接寫(xiě)诫隅,如要如下操作:
class Human {
constructor(name){
this.name = name
}
get race(){
return '人類(lèi)'
}
run(){}
}
然后一個(gè)person = new Human('Enoch')
就呈現(xiàn)了如下結(jié)構(gòu)
而我們?nèi)绻?ES 5 的寫(xiě)法就很容易:
function Human(name){
this.name = name
}
Human.prototype.race = '人類(lèi)'
person
的結(jié)構(gòu)如下:
(END)