這一篇鴿了兩個周,實在不能再拖下去了。之因此會拖這麼久,除了一方面這一塊不像以前那樣只是一個小知識點,另外一方面是總想着能寫出點什麼新東西。如今發現,我還只是一名技術領域的追隨者,可以勉強跟得上技術的潮流就已經值得慶幸了;我所學習的東西,大可能是五六年前的標準,七八年前的框架,十幾年前的思想。因此,看清本身的位置,立足當下,把它看成本身的學習總結。javascript
JavaScript 是一門動態語言,動態語言的哲學決定了咱們很難用一些靜態語言類的思想去操縱 JavaScript 的對象。雖然 es6 給出了不少新特性,這些新特性能夠幫助咱們在必定程度上模擬類,但不能忽視的是,它們是創建在原型的基礎之上的。深刻地瞭解原型,而不是一味地迴避,可讓咱們更好地使用 JavaScript。java
接下來我會按照本身的理解,整理關於原型的線索,內容以下:es6
先來了解下原型。之因此把原型放在對象前面,是由於我在學習 JavaScript 對象的相關知識時發現,它徹底繞不開原型。先對原型創建個大概印象,再以它爲工具,能夠更好地發現隱藏在JS語法背後的奧祕。瀏覽器
因此,原型是什麼?大概你已經在不一樣的場合見識過原型的介紹了,這裏請看MDN官方文檔關於對象原型的介紹:bash
JavaScript 常被描述爲一種基於原型的語言 (prototype-based language)——每一個對象擁有一個原型對象,對象以其原型爲模板、從原型繼承方法和屬性。原型對象也可能擁有原型,並從中繼承方法和屬性,一層一層、以此類推。這種關係常被稱爲原型鏈 (prototype chain),它解釋了爲什麼一個對象會擁有定義在其餘對象中的屬性和方法。markdown
準確地說,這些屬性和方法定義在Object的構造器函數(constructor functions)之上的
prototype
屬性上,而非對象實例自己。框架在傳統的 OOP 中,首先定義「類」,此後建立對象實例時,類中定義的全部屬性和方法都被複制到實例中。在 JavaScript 中並不如此複製——而是在對象實例和它的構造器之間創建一個連接(它是
__proto__
屬性,是從構造函數的prototype
屬性派生的),以後經過上溯原型鏈,在構造器中找到這些屬性和方法。函數注意:理解對象的原型(能夠經過
Object.getPrototypeOf(obj)
或者已被棄用的__proto__
屬性得到)與構造函數的prototype
屬性之間的區別是很重要的。前者是每一個實例上都有的屬性,後者是構造函數的屬性。也就是說,Object.getPrototypeOf(new Foobar())
和Foobar.prototype
指向着同一個對象。工具
初學者看這段介紹確定是懵的,不要緊接下來一一做介紹。學習
這裏仍是先沿用官方的說明和例子。
在javascript中,函數能夠有屬性(注:能夠認爲JS中函數是特殊的對象)。每一個函數都有一個特殊的屬性叫做 prototype
(注,mdn中文文檔這裏翻譯爲【原型】,但我認爲不翻譯比較好,後面也是如此),正以下面所展現的。
function Foo(){}
console.log( Foo.prototype );
// 你如何聲明函數並不重要,
// 在javascript中函數都會有一個默認的
// prototype 屬性。
var Foo = function(){};
console.log( Foo.prototype );
複製代碼
它們都會返回同一個對象。是的沒錯,這些函數的 prototype
指向了同一個特殊的對象,通常稱爲【原型對象】:
{
constructor: ƒ Foo(),
__proto__: {
constructor: ƒ Object(),
hasOwnProperty: ƒ hasOwnProperty(),
isPrototypeOf: ƒ isPrototypeOf(),
propertyIsEnumerable: ƒ propertyIsEnumerable(),
toLocaleString: ƒ toLocaleString(),
toString: ƒ toString(),
valueOf: ƒ valueOf()
}
}
複製代碼
上面那些奇奇怪怪的函數,有些等會會說起到,有些本文沒法顧及。咱們先重點來看看該原型對象的兩個屬性名:constructor
和 __proto__
。
前者 constructor
,能夠翻譯成【構造器】,看起來它又指回原來的函數了。它們的關係彷佛是這樣的:
因此,是否是就意味着一個函數的原型對象的 constructor
必定指向該函數呢?確定不是,既然它是一個可訪問屬性,那麼它的對象確定就能夠修改。具體怎麼修改,這裏暫且不提,若是有讀者感興趣能夠留言,或者自行閱讀《你不知道的 JavaScript(上)》第二部分第5、六章。
後者 __proto__
,看起來是一個很奇怪的屬性名,它有另外一個稱呼你可能見過,[[Prototype]]
。嗯?這個怎麼看起來和以前的 prototype
屬性那麼像啊。二者有什麼關聯嗎?
這裏我暫時找不到關於 [[Prototype]]
的官方定義,ECMAScript 可能有但我懶得找了。不過,不管是 MDN 仍是《你所不知道的 JavaScript》都提到它是一個內部屬性。什麼意思呢?雖然JS中沒有私有屬性的概念,可是每一個對象都有一些內部屬性,其中就有 [[Prototype]]
。在 ES 標準中,該屬性你是沒法經過常規的訪問方式訪問和設置的——包括點訪問法和括號訪問法(.__proto__
不是標準實現,它只是個別瀏覽器廠商的內部實現)。甚至在 ES5 以前,除了 new
操做外沒法經過其它途徑操做該屬性(後文會介紹 ES5 支持的新方法)。
上面說到,__proto__
,即 [[Prototype]]
是一個屬性,也指向了一個對象。該對象也有一個 construcotr
屬性,難道它也是原型對象?沒錯,而且JS 還有不少內置函數,包括 Function
的 __proto__
都指向這個原型對象。後文會提到,它其實就是 Object
的原型對象。
你可能看得雲裏霧裏,不要緊看看下面代碼你就清楚了:
console.log(Foo.prototype.__proto__ === Object.prototype) // 谷歌瀏覽器下
// true
複製代碼
再回到 MDN 官方文檔的介紹,裏面有一句話提到JS原型的複製機制:
在對象實例和它的構造器之間創建一個連接(它是
__proto__
屬性,是從構造函數的prototype
屬性派生的)
這句話怎麼理解呢?請看下面這個代碼示例,環境是谷歌開發者工具:
var Foo = function(){}
// undefined
var foo = new Foo // 無參數時可省略括號
// undefined
Foo.prototype === foo.__proto__
// true
複製代碼
看起來它們之間的關係是這樣的:
須要注意的是,foo
沒有屬性 prototype
。這裏官方文檔也有提到:**[[Prototype]]
是每一個實例上都有的屬性,prototype
是構造函數的屬性。**這裏,每一個實例應是指對象(包括函數),也就是說JS中每一個對象都有 [[Prototype]]
屬性(並且是內部屬性)。你可能會好奇,那 Foo
的 [[Prototype]]
屬性指向什麼呢?
這裏其實就涉及到原型鏈的知識了。
那麼,Foo
的 [[Prototype]]
屬性指向什麼呢?前文說到,Foo
的原型對象的 [[Prototype]]
屬性指向 Object
的原型對象。那麼若是不是該函數自己是否也指向 Object
的原型對象呢?嘗試下看看:
console.log(Foo.__proto__ === Object.prototype) // 谷歌瀏覽器下
// false
複製代碼
看起來不是。這裏直接給出答案吧,它其實指向 Function
的原型對象,你能夠用一樣的方法檢測一下:
console.log(Foo.__proto__ === Function.prototype) // 谷歌瀏覽器下
// true
複製代碼
估計有些人會滿腦子疑問,那 Function
的 [[Prototype]]
,及該函數原型對象的 [[Prototype]]
都指向什麼呢(包括 Object
、foo
等等)?有兩個方法,一個是你本身一個一個找,另外一個是請你看下面這幅圖,而且試着作下驗證:
你能夠着重看下 Function
、Object
和 Foo
這幾個函數的原型對象之間的關係。看的時候確定有不少疑問,**爲何光線條的樣式就有三種呢?**不要緊先放下繼續看下文。
不知道看完上文,特別是上圖,你對【原型鏈】是否有必定的認識?也許你腦中會反應過來剛剛說起的函數的 prototype
,及全部實例即對象的 [[Prototype]]
(請記住,二者不一樣一回事)。沒錯,當咱們談及原型鏈時,確定繞不開這兩個屬性。
工欲善其事,必先利其器。咱們先了解下操縱這兩個屬性的工具。前者其實不用說了,它是一個可以直接修改的屬性;後者前文說過了,它是一個內部屬性,ES5 後有三個標準實現能夠操縱它:
除此以外,在 ES6 以前沒有 setPrototypeOf
方法時,有兩個替代性的方法:
[[Prototype]]
的方法)前文說過,__proto__
是個別瀏覽器廠商的內部實現,還不是標準。綜上,這些方法,基本上就是 es6 後可以瞭解 [[Prototype]]
的工具了。它們的做用應該很好猜,若是不肯定的話還請自行搜索一下吧。
那麼,咱們如今再回到最開始的問題,原型鏈是什麼?
這裏用一些很容易搞錯的問題,做爲引子。請看下面代碼(後面不特殊說明,環境都是谷歌開發者工具):
var Foo = function(){}
console.log(Foo.constructor === Function)
// true
console.log(Object.constructor === Function)
// true
console.log(Function.constructor === Function)
// true
複製代碼
看起來很奇怪,尤爲是最後一個。嗯,前文的原型對象彷佛有提到這個 constructor
,像 Foo
的原型對象的 constructor
正是它自己。前文也提到,該屬性是可直接訪問和修改的。看起來,該屬性應該只有原型對象纔有的,這些構造函數應該不可能有。嗯,這話說對了一半,請看下例:
Foo.hasOwnProperty("constructor")
// false
Object.hasOwnProperty("constructor")
// false
Function.hasOwnProperty("constructor")
// false
複製代碼
問題就來了,既然這三個函數都沒有該屬性,爲何以前的例子又是那樣輸出的呢?瀏覽器確定沒犯毛病,問題的根源就在原型鏈上。
回到原型鏈,咱們先找找 MDN 官方文檔中對它的描述:
每一個對象擁有一個原型對象,對象以其原型爲模板、從原型繼承方法和屬性。原型對象也可能擁有原型,並從中繼承方法和屬性,一層一層、以此類推。這種關係常被稱爲原型鏈 (prototype chain),它解釋了爲什麼一個對象會擁有定義在其餘對象中的屬性和方法。
簡單點說,原型鏈可讓一個對象訪問定義在其它對象中的屬性和方法。具體的訪問過程是怎樣的,這裏不細講,下一篇博客【對象部分】會討論這個問題。你這裏就能夠先按照你的喜愛簡單理解。
那麼,咱們用這個剛認識的原型鏈來剖析一下上面的問題吧。咱們都知道了,那三個函數確定是沒有 constructor
屬性的。那麼根據原型鏈的定義,它們確定是訪問了其它對象的該屬性,並且極可能仍是同一個對象。那是哪一個對象呢?我不妨再放一次圖(但是辛辛苦苦作了兩個小時的),這一副重點描了一個紅框,它其實就同時是這三個函數所訪問的那個「受害人」,以及三個紅圈,它們所在的三條線其實就是形成上述問題的「罪魁禍首」。
嗯,原型鏈簡直是魔鬼。這裏我爲了方便你們理解,將製做的這幅圖一部分線條用紅色和藍色描出來。紅線是關於 Object
的原型鏈,藍線是關於 Function
的原型鏈。
當你理解這幅圖時,你也就理解了,爲何函數能夠訪問一些特殊方法,對象又能夠訪問一些特殊方法。其實都是原型鏈的功勞。至於都有哪些特殊方法,我貼一副《你不知道的 JavaScript》書中的插圖:
其中左邊的紅色橢圓圈住 Function
的原型對象,右邊的紅色方框圈住 Object
的原型對象,省略號部分可自行搜索。另外,上面紅色方框圈住的 construct
是我認爲有問題之處。根據以前的代碼示例,Object
沒有 constructor
,這裏應該指的是原型委託,但委託的對象又出了差錯。總之,還請讀者自行辨認。
短短一篇博客還分前言後記是挺搞笑的。只不過我這篇確實寫了兩三天,工做量挺大的,光是例圖可能就花了三個多小時,因此也想請看了此篇後,以爲有幫助的讀者給我點個贊吧~
另外作一個預告,下一篇應該是關於 JS 的對象了,它與原型本緊密結合,應放一塊兒寫纔對;只不過此篇就寫了七千多字(markdown格式的統計,包括字母),再寫下去太長了。嗯,就這樣吧,感謝慧鑑。