『新手日記』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