前文:
繼承是OO(面向?qū)ο螅┱Z言中的一個最為人津津樂道的概念驻右。許多OO語言都支持兩種繼承方式:接口繼承和實(shí)現(xiàn)繼承。接口繼承只繼承方法簽名,而實(shí)現(xiàn)繼承則繼承實(shí)際的方法摔敛。但在JS中函數(shù)沒有簽名,在ECMAScript中無法實(shí)現(xiàn)接口繼承全封,只支持實(shí)現(xiàn)繼承马昙,而且其實(shí)現(xiàn)繼承主要是依靠原型鏈來實(shí)現(xiàn)的。
要理解Javascript的繼承機(jī)制刹悴,必須從它的發(fā)展史說起行楞。
1994年,網(wǎng)景公司(Netscape)發(fā)布了Navigator瀏覽器0.9版颂跨。這是歷史上第一個比較成熟的網(wǎng)絡(luò)瀏覽器敢伸,轟動一時。網(wǎng)景公司急需一種網(wǎng)頁腳本語言恒削,使得瀏覽器可以與網(wǎng)頁互動池颈。工程師Brendan Eich負(fù)責(zé)開發(fā)這種新語言。他覺得钓丰,沒必要設(shè)計得很復(fù)雜躯砰,這種語言只要能夠完成一些簡單操作就夠了,比如判斷用戶有沒有填寫表單携丁。
Brendan Eich的選擇
如果真的是一種簡易的腳本語言琢歇,其實(shí)不需要有"繼承"機(jī)制。但是梦鉴,Javascript里面都是對象李茫,必須有一種機(jī)制,將所有對象聯(lián)系起來肥橙。所以魄宏,Brendan Eich最后還是設(shè)計了"繼承"。
但是存筏,他不打算引入"類"(class)的概念宠互,因?yàn)橐坏┯辛?類",Javascript就是一種完整的面向?qū)ο缶幊陶Z言了椭坚,這好像有點(diǎn)太正式了予跌,而且增加了初學(xué)者的入門難度。
他考慮到善茎,C++和Java語言都使用new命令券册,生成實(shí)例。
C++的寫法是:
ClassName *object = new ClassName(param);
Java的寫法是:
Foo foo = new Foo();
因此,他就把new命令引入了Javascript汁掠,用來從原型對象生成一個實(shí)例對象略吨。但是,Javascript沒有"類"考阱,怎么來表示原型對象呢翠忠?
這時,他想到C++和Java使用new命令時乞榨,都會調(diào)用"類"的構(gòu)造函數(shù)(constructor)秽之。他就做了一個簡化的設(shè)計,在Javascript語言中吃既,new命令后面跟的不是類考榨,而是構(gòu)造函數(shù)。
prototype對象
prototype對象的引入:所有實(shí)例對象需要共享的屬性和方法鹦倚,都放在這個對象中河质,那些不需要共享的屬性和方法,就放在構(gòu)造函數(shù)中震叙。以此來模擬類掀鹅。
function Animal(name) {
this.name = name
}
Animal.prototype.getName = function() {
console.log(this.name)
}
var animal1 = new Animal('Kate')
var animal2 = new Animal('Lucy')
//對象animal1 和 animal2共享方法getName
animal1.getName()
animal2.getName()
proto寫入es6標(biāo)準(zhǔn)
當(dāng)一個對象被創(chuàng)建時,它的protp屬性和內(nèi)部屬性[[prototype]]指向相同的對象(也就是它的構(gòu)造函數(shù)的prototype屬性)媒楼。改變proto屬性的值同時也會改變內(nèi)部屬性[[prototype]]的值乐尊,除非該對象是不可擴(kuò)展的。
在ES5中划址,所有構(gòu)造函數(shù)的proto都指向Function.prototype
在ES6中扔嵌,構(gòu)造函數(shù)的proto指向它的父類構(gòu)造函數(shù)
obj.__proto__ === obj.[[prototype]]
// ES5
Cat.__proto__ === Function.prototype
// ES6
Cat.__proto__ === Animal
構(gòu)造函數(shù)繼承
有四種方式可以實(shí)現(xiàn)構(gòu)造函數(shù)的繼承
1.調(diào)用apply方法
function Animal() {
this.species = '動物'
}
Animal.prototype.getName = function() {
console.log('我是動物')
}
function Cat() {
Animal.apply(this, arguments)
}
var cat = new Cat()
cat.species // 動物
cat.getName() // undefined
這種方法可以繼承父類構(gòu)造函數(shù)的屬性,但是無法繼承prototype屬性夺颤,即父類中共享的方法和屬性
2.改寫prototype對象
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
這是最常用的方法來模擬單繼承痢缎,缺點(diǎn)是始終要保留Animal的對象,如果Animal對象比較大時世澜,會消耗部分內(nèi)存(其實(shí)很少)独旷,并且沒有實(shí)現(xiàn)多繼承
3.直接繼承prototype
Cat.prototype = Animal.prototype
Cat.prototype.constructor = Cat
缺點(diǎn)是當(dāng)修改了Cat.prototype上的方法時會影響Animal.prototype
4.利用空對象作中介
var F = function(){}
F.prototype = Animal.prototype
Cat.prototype = new F()
Cat.prototype.constructor = Cat
缺點(diǎn)是無法繼承父類封裝的屬性
若要實(shí)現(xiàn)封裝屬性和共享同時繼承到子類中,就需要同時結(jié)合上面的1和4宜狐,請使用jqury的extend方法或者其他深拷貝方法势告。
繼承
關(guān)于繼承蛇捌, ES5和ES6的區(qū)別
ES5:先構(gòu)造子類的實(shí)例對象this抚恒,然后再將父類的方法添加到this上面
ES6:先創(chuàng)造父類的實(shí)例對象this(所以必須先調(diào)用super方法),然后再用子類的構(gòu)造函數(shù)修改this
// ES6
class Cat extends Animal {
constructor() {
super(this)
}
...
}
// ES5
function Cat() {
...
}
Cat.prototype = new Animal()
Cat.prototype.constructor = Cat
參考文章:Javascript繼承機(jī)制的設(shè)計思想