『新手日記』Day-24 JavaScript 的原型鍊

聽說我們的富奸又要開始連載了,庫拉皮卡終於可以下船了~~
此篇文章是節錄這篇文章的內容所做的重點整理,如有興趣可以點進去閱讀原版資料。
JavaScript的前世今生
1994年,網景公司(Netscape)發布了Navigator瀏覽器0.9版。但是,這個版本的瀏覽器只能用來瀏覽,不具備與訪問者互動的能力。因此網景公司急需一種網頁腳本語言,使得瀏覽器可以與網頁互動而工程師Brendan Eich負責開發這種新語言。
當時C++是最流行的語言,Java語言的1.0版即將於第二年推出,Brendan Eich無疑受到了影響,Javascript裡面所有的數據類型都是object。這時他遇到了一個難題,到底要不要設計”繼承”機制呢?因為一種簡易的腳本語言,是不需要有”繼承”機制,而且一旦有了,Javascript就是一種物件導向程式設計(OOP)了,增加了初學者的入門難度。但是,Javascript裡面都是object,必須有一種機制,將所有對象聯繫起來。所以,Brendan Eich最後還是設計了”繼承”。
JavaScript 中的 class
但同時他卻不打算引入class的概念,他考慮到C++和Java語言都使用new命令,生成instance。因此,他就把new命令引入了Javascript,用來從原型對像生成一個instance object。但是,Javascript沒有”class”,怎麼來表示prototype object呢?
這時,他想到C++和Java使用new命令時,都會調用”class”的構造函數(constructor)。他就做了一個簡化的設計,在Javascript語言中,new命令後面跟的不是class,而是構造函數。
舉例來說,現在有一個叫做DOG的構造函數,表示狗對象的原型。
1  | function DOG(name){  | 
對這個構造函數使用new,就會生成一個狗對象的instance。
1  | var dogA = new DOG('大毛');  | 
注意構造函數中的this關鍵字,它就代表了新創建的prototype object。
探究原理
了解了JavaScript的前世今生後,我們來看看下面內容:
1  | function Person(name, age) {  | 
name 跟 age 這兩個屬性,很明顯每一個 instance 都會不一樣的。但在 log 這個 method中其實每一個 instance 彼此之間可以共享的,雖然 nick 跟 peter 的 log 這個 function 是在做同一件事,但其實還是佔用了兩份空間,意思就是他們其實是兩個不同的 function。
如果我要兩個都是一樣那怎麼辦呢?我們可以把這個 function 抽出來,變成所有 Person 都可以共享的方法。這邊我們要用一個東西叫做prototype。
1  | function Person(name, age) {  | 
看到這裡我就有一個疑問,呼叫nick.log()的時候,JavaScript 是怎麼找到這個 function 的?
因為 nick 這個 instance 本身並沒有 log 這個 function。但根據 JavaScript 的機制,nick 是 Person 的 instance,所以如果在 nick 本身找不到,它會試著從Person.prototype去找。所以nick 跟Person.prototype 應該會透過某種方式連接起來,而這個連接的方式,就是__proto__。(附註:比較好的方式是用Object.getPrototypeOf(),但這邊為了方便起見,還是使用比較常見的__proto__,更詳細的說明可參考:MDN: Object.prototype.proto)
1  | function Person(name, age) {  | 
nick 的__proto__會指向Person.prototype,所以在發現 nick 沒有 log 這個 method 的時候,JavaScript 就會試著透過__proto__找到Person.prototype,去看Person.prototype裡面有沒有 log 這個 method。
那假如Person.prototype還是沒有呢?那就繼續依照這個規則,去看Person.prototype.__proto__裡面有沒有 log 這個 method,就這樣一直不斷找下去。找到時候時候為止?找到某個東西的__proto__是 null 為止。意思就是這邊是最上層了。
而上面這一條透過__proto__不斷串起來的鍊,就叫做『原型鍊』。透過這一條原型鍊,就可以達成類似繼承的功能,可以呼叫自己 parent 的 method。
名詞解釋:
instanceof
A instanceof B 就是拿來判斷 A 是不是 B 的 instance
constructor
每一個 prototype 都會有一個叫做constructor的屬性,例如說Person.prototype.constructor。而這個屬性就會指向構造函數。Person.prototype的構造函數是什麼?當然就是Person囉。
new
有了原型鍊的概念之後,就不難理解new這個關鍵字背後會做的事情是什麼。
假設現在有一行程式碼是:var nick = new Person('nick');,那它有以下幾件事情要做:
- 創出一個新的 object,我們叫它 O
 - 把 O 的 
__proto__指向 Person 的 prototype,才能繼承原型鍊 - 拿 O 當作 context,呼叫 Person 這個建構函式
 - 回傳 O
 
把所有的資料都看過一遍,但感覺還要再看一次還有些地方不是很了解,大家有興趣可以到看一下原作者所寫的,他還有附上超多參考資料。
github.com