你知道的JS原型鏈--對象與原型
最近一直在以忙為借口犯懶,到了清明節(jié)終于捱不過去了,所以還是準(zhǔn)備寫一篇文章來總結(jié)一下。最近開發(fā)的一個項目涉及到一些關(guān)于es6繼承的問題。得益于JavaScript強(qiáng)大的原型機(jī)制,我們可以很輕易的模仿出類的特性,當(dāng)然由于是基于原型實現(xiàn)的class語法糖,所以我們可以做出一些特別騷氣的操作,比如動態(tài)繼承。
例如
class A {
constructor(){
console.log('this is A');
}
}
// class factory
function InheritMaker(constructor){
return class B extends constructor {
constructor(){
super();
console.log('this is B');
}
}
}
const a = new A(); // this is A
const B = InheritMaker(A);
const b = new B(); // this is A; this is B
說實話,我一直在思考這種方式到底是不是違反了類的初衷。類是靜態(tài)的,但是我們在 JavaScript中可以在運行時動態(tài)選擇繼承,這使得類的行為變得不確定: 我無法知道我的類到底會實現(xiàn)什么樣子的功能,只用運行時才知道。這種見鬼的實現(xiàn)方式在Java中絕對會報錯報到死,但是在JavaScript中卻可以平穩(wěn)運行。
說了這么多,這種靈活不失騷氣的實現(xiàn)方式還是得益于JavaScript中強(qiáng)大的原型機(jī)制。接下來,我希望來說清楚JavaScript中的原型到底是怎么回事,以及拋磚引玉的對于es6的class實現(xiàn)進(jìn)行一些粗淺的探討。
對象與原型
我最一開始接觸JavaScript的時候不知道什么是原型,把它和對象當(dāng)作兩個概念來看待。
我們都知道,一個類new出來的實例是一個對象。
我們來看下EcmaScript對于對象的定義:
無序?qū)傩缘募?,其屬性可以包含基本值,對象或者函?shù)
原型就是無需屬性的集合,原型就是對象,原型就是實例
下面讓我們來拋開es6談一談對象與原型。
我們都知道,對象是我們用class ‘new’出來的,例如我們想實現(xiàn)創(chuàng)建兩個人,張三和李四,他們都擁有相同的屬性,但是屬性的值不同,所以我們將他們歸為一個‘類’,在某些靜態(tài)語言中大致長這樣:
class People {
constructor(string name, int age) {
this.name = name;
this.age = age;
}
}
const zhangsan = new People('張三', 12);
const lisi = new People('李四', 14);
這樣兩個人就創(chuàng)建好了。
但是JavaScript沒有類的概念怎么破?
下面用es5實現(xiàn)了同樣的 效果:
function Peole(name, age) {
this.name = name;
this.age = age;
}
var zhangsan = new People('張三', 12);
var lisi = new People('李四', 14);
上面創(chuàng)建了一個構(gòu)造函數(shù),實現(xiàn)了相同的效果。
對于JavaScript來說,People是一個實例,它的構(gòu)造函數(shù)是Function,因此它擁有__proto__,又是(構(gòu)造)函數(shù),所以擁有prototype,值得說明的一點是只有函數(shù)擁有prototype。
那么prototype與__proto__又有什么區(qū)別呢?
prototype & proto
JavaScript中有兩個指針,prototype與__proto__,我在初學(xué)的時候一直對于這兩個指針一直十分困惑,現(xiàn)在我希望可以解釋清楚這些事情。
對于People構(gòu)造函數(shù)來說,它在被創(chuàng)建的時候擁有prototype屬性,prototype的constructor指向它本身:

如果用一種不太準(zhǔn)確但容易理解的方式來解釋,prototype才是People的類,上面擁有People所定義的一切方法,例如我們舉個例子:
class People {
constructor(name, age){
this.name = name;
this.age = age;
}
changeName(newName){
this.name = newName;
}
}
我們在上面多聲明了一個方法changeName,因此上面的圖改為:

這就是prototype
但是我們也說了People不僅僅是個構(gòu)造函數(shù),它還是Function的實例,對于一個實例來說,它需要知道創(chuàng)建它的類是什么。
注意這里非常容易糊涂:
prototype指向的是它作為構(gòu)造函數(shù)的類
_proto_指向的是創(chuàng)建它的類
其實這也非常容易理解,因為對于JavaScript來說,函數(shù)也是一個對象。也就是說函數(shù)也是被‘new’出來的。創(chuàng)建它的類是Function
就是下面的這個Function:
const People = new Function();
因此上面的圖可以更改為:

同樣的,F(xiàn)unction類的構(gòu)造函數(shù)也是一個函數(shù)。。所以Function構(gòu)造函數(shù)的prototype指向Function類,而它的__proto__也指向它的類:

因此你可以試一下:
Function.prototype === Function.__proto__; // true
我們剛才也說過了,prototype 也是一個對象,也是被'new'出來的,因為是對象,所以創(chuàng)造它的構(gòu)造函數(shù)是Object,因此上圖可以更改為:

因此就出現(xiàn)了一個問題,創(chuàng)建Object的類是個毛?
說的再玄幻一點就是創(chuàng)建對象的是個什么玩應(yīng)?
是null
從無到有。
小結(jié)
因此原型的本質(zhì)可以概括為:
JavaScript中的類也是個對象
當(dāng)然這不太準(zhǔn)確,我會在下節(jié)做闡述,先這么理解就好。