JavaScript是一門面向?qū)ο蟮恼Z(yǔ)言,繼承是面向?qū)ο蟮囊淮筇匦裕菄?yán)格來(lái)講JavaScript中卻沒(méi)有通常含義上的繼承伤塌,只能模擬繼承,即使ES6之后有了class轧铁,其實(shí)現(xiàn)和其他面向?qū)ο笳Z(yǔ)言依然有本質(zhì)不同每聪。而JavaScript中模擬繼承的方式則是通過(guò)原型鏈
1、什么是原型和原型鏈齿风?
JavaScript中药薯,對(duì)象實(shí)例在創(chuàng)建的時(shí)候會(huì)關(guān)聯(lián)一個(gè)[[Prototype]]屬性,即對(duì)象的原型
ES6之前標(biāo)準(zhǔn)并沒(méi)有定義獲取原型對(duì)象的方法救斑,但是基本所有瀏覽器都支持通過(guò)__proto__
來(lái)獲取童本,而ES6之后則提供了Object.getPrototypeOf()方法
當(dāng)我們對(duì)對(duì)象進(jìn)行“對(duì)象.屬性名”操作時(shí),如:
var a = {
name = 'smartzheng';
}
console.log(a.name);
這里會(huì)調(diào)用a對(duì)象的[[Get]]屬性脸候,如果a對(duì)象含有name屬性穷娱,那么a.name直接返回這個(gè)屬性,如果a中沒(méi)有該屬性的話就會(huì)使用到原型了:會(huì)在a.__proto__
得到的原型對(duì)象中去尋找name屬性运沦,如果還找不到泵额,就會(huì)在a.__proto__.__proto__
(如果有的話)中去找...當(dāng)然這也有一個(gè)終點(diǎn),也就是Object.prototype:這就形成了原型鏈
2茶袒、原型鏈和原型繼承
繼續(xù)詳細(xì)分析一下JavaScript中的原型和原型鏈
我們知道梯刚,創(chuàng)建對(duì)象常用的有3種方式:字面量,new和Object.create()薪寓;上面說(shuō)到亡资,對(duì)象在創(chuàng)建的時(shí)候會(huì)有一個(gè)[[Prototype]]屬性,它其實(shí)是對(duì)另一個(gè)對(duì)象的引用向叉,我們稱之為該實(shí)例的原型對(duì)象
分別來(lái)分析一下前面提到的三種方法創(chuàng)建對(duì)象之后默認(rèn)關(guān)聯(lián)的原型對(duì)象:
1)字面量形式
var a = {};
console.log(a.__proto__ === Object.prototype); //true
console.log(a.__proto__);
下面是a.__proto__
打印出的結(jié)果:
{
constructor: ?,
__defineGetter__: ?,
__defineSetter__: ?,
hasOwnProperty: ?,
__lookupGetter__: ?,
…
}
展開下一級(jí)之后為:
{
constructor: ? Object()
hasOwnProperty: ? hasOwnProperty()
isPrototypeOf: ? isPrototypeOf()
propertyIsEnumerable: ? propertyIsEnumerable()
toLocaleString: ? toLocaleString()
toString: ? toString()
valueOf: ? valueOf()
__defineGetter__: ? __defineGetter__()
__defineSetter__: ? __defineSetter__()
__lookupGetter__: ? __lookupGetter__()
__lookupSetter__: ? __lookupSetter__()
get __proto__: ? __proto__()
set __proto__: ? __proto__()
}
這里可以發(fā)現(xiàn)幾個(gè)熟悉的方法锥腻,如valueOf、toString母谎、toLocaleString瘦黑;其實(shí)這些都是Object的prototype中的方法,而我們使用字面量方式創(chuàng)建的對(duì)象實(shí)際上在創(chuàng)建時(shí)也默認(rèn)將Object.prototype賦值給了它的__proto__
,所以a.__proto__ === Object.prototype
為true幸斥,這也是為什么所有對(duì)象都默認(rèn)可以使用前面提到的幾個(gè)方法的原因
2)new
var a = new Object();
console.log(a.__proto__ === Object.prototype); //true
顯然匹摇,new和字面量創(chuàng)建對(duì)象一樣,都是將Object.prototype賦值給了實(shí)例的原型對(duì)象
3)Object.create()
var a = Object.create(Object.prototype);
console.log(Object.getPrototypeOf(a) === Object.prototype); //true
可見甲葬,Object.create()是將傳入對(duì)象直接賦值給了新實(shí)例的原型對(duì)象屬性
通過(guò)上面的分析可以看出廊勃,三種方式創(chuàng)建對(duì)象都為實(shí)例默認(rèn)關(guān)聯(lián)了一個(gè)原型對(duì)象,通過(guò)__proto__或者Object.getPrototypeOf()可以獲取经窖,如果是字面量模式坡垫,則新實(shí)例的原型對(duì)象即Object.prototype,但是new和Object.create()的話則可以自定義默認(rèn)關(guān)聯(lián)的原型對(duì)象:
function Foo(){
//...
}
var f1 = new Foo();
var f2 = Object.create(Foo.prototype);
3画侣、“類”冰悠、“構(gòu)造函數(shù)”和“繼承”
最后再看一個(gè)全面的例子:
function Foo(){
//...
}
var f1 = new Foo();
var f2 = Object.create(Foo.prototype);
console.log(Foo.prototype) //{constructor: ? Foo(), __proto__: Object}
console.log(Foo.prototype.__proto__ === Object.prototype); //true
console.log(Object.getPrototypeOf(f1) === Foo.prototype); //true
console.log(Object.getPrototypeOf(f2) === Foo.prototype); //true
在JavaScript中經(jīng)常把首字母大寫的function稱之為“類”,例如上面的Foo配乱,這里Foo.prototype包含兩個(gè)屬性溉卓,constructor屬性指向Foo()自身,當(dāng)我們使用new調(diào)用Foo()時(shí)搬泥,實(shí)際上執(zhí)行了以下幾步:
1)創(chuàng)建一個(gè)新對(duì)象:
const f1 = {}
2)設(shè)置新對(duì)象的constructor屬性為構(gòu)造函數(shù)的名稱的诵,設(shè)置新對(duì)象的__proto__屬性指向構(gòu)造函數(shù)的prototype對(duì)象:
f1.constructor = Foo;
f1.__proto__ = Foo.prototype
3)使用新對(duì)象調(diào)用函數(shù),函數(shù)中的this被指向新實(shí)例對(duì)象:
Foo.call(f1)
4)將初始化完畢的新對(duì)象地址佑钾,保存到等號(hào)左邊的變量中
很多人習(xí)慣稱Foo為一個(gè)類,它的實(shí)例f1的原型對(duì)象對(duì)Foo的prototype烦粒,F(xiàn)oo也有一個(gè)__proto__屬性休溶,指向的是Object.prototype,根據(jù)前面的邏輯扰她,在f1若找不到某個(gè)屬性兽掰,則會(huì)在f1.__proto__,也就是Foo.prototype中尋找該屬性徒役,如果從f1.__proto__中取不到對(duì)應(yīng)的屬性孽尽,則會(huì)在f1.__proto__.__proto__即Object.prototype中尋找該屬性,若還找不到忧勿,則返回undefined
這一表現(xiàn)確實(shí)和繼承很像:有一個(gè)頂級(jí)的父類Object杉女,所有對(duì)象都是它的實(shí)例或者它子類的實(shí)例,所有這些實(shí)例都可以使用這個(gè)頂級(jí)父類中定義的方法鸳吸,比如上面的Foo可以理解成繼承自O(shè)bject熏挎,其實(shí)例f1可以使用Foo.prototype中定義的屬性,如果找不到則使用父類Object.prototype中的屬性晌砾;這雖然看起來(lái)很像面向?qū)ο缶幊讨械睦^承坎拐,但是JavaScript中實(shí)際上沒(méi)有繼承(實(shí)際上從new的工作原理看來(lái)JavaScript也算不上有類,并沒(méi)有類實(shí)例的拷貝操作),只是通過(guò)原型鏈的方式實(shí)現(xiàn)了類似繼承的表現(xiàn)
在Java中哼勇,所有類都繼承自O(shè)bject都伪,所有的類的實(shí)例對(duì)象都可以使用Object中的方法,比如hashCode积担,equals等陨晶,但是與JavaScript不同的是,在Java中磅轻,繼承的含義是指每一個(gè)子類中都有父類方法和屬性的一份拷貝珍逸,在父類中定義了一個(gè)方法,子類可以直接使用也可以重寫覆蓋聋溜,但是子類都是調(diào)用的自己的屬性和方法
而在JavaScript中其實(shí)只是建立了對(duì)象之間的關(guān)聯(lián)谆膳,當(dāng)在對(duì)象自己的屬性中找不到想要獲取的屬性時(shí),就會(huì)去關(guān)聯(lián)在它的__proto__
屬性上的對(duì)象中去找撮躁,更像是一種委托
4漱病、總結(jié)
下面這張圖很形象的說(shuō)明了文中各個(gè)角色的關(guān)系:
1)每一個(gè)函數(shù)通過(guò)new會(huì)創(chuàng)建一個(gè)以該函數(shù)的prototype為原型對(duì)象的實(shí)例,可以通過(guò)對(duì)象的__proto__屬性獲取該原型對(duì)象
2)函數(shù)的prototype中有兩個(gè)屬性把曼,constructor指向自身杨帽,具體調(diào)用時(shí)機(jī)和方式參見上文關(guān)于new的分析,另一個(gè)屬性__proto__指向上一級(jí)關(guān)聯(lián)的原型對(duì)象(最高為Object.prototype)
3)當(dāng)實(shí)例觸發(fā)[[Get]]操作時(shí)首先從自己內(nèi)部查找屬性嗤军,若找不到則依次通過(guò)__proto__往上找注盈,從而形成了原型鏈