Jeff的隨手筆記

學習當一個前端工程師

0%

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

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

https://miro.medium.com/max/1400/1*YG13dBl8LH3Ta47VeQ96yQ.png

聽說我們的富奸又要開始連載了,庫拉皮卡終於可以下船了~~

此篇文章是節錄這篇文章的內容所做的重點整理,如有興趣可以點進去閱讀原版資料。

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
2
3
function DOG(name){
this.name = name;
}

對這個構造函數使用new,就會生成一個狗對象的instance。

1
2
var dogA = new DOG('大毛');
alert(dogA.name); // 大毛

注意構造函數中的this關鍵字,它就代表了新創建的prototype object。

探究原理

了解了JavaScript的前世今生後,我們來看看下面內容:

1
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age) {
this.name = name;
this.age = age;
this.log = function () {
console.log(this.name + ', age:' + this.age);
}
}

var nick = new Person('nick', 18);
var peter = new Person('peter', 20);

console.log(nick.log === peter.log) // false

name 跟 age 這兩個屬性,很明顯每一個 instance 都會不一樣的。但在 log 這個 method中其實每一個 instance 彼此之間可以共享的,雖然 nick 跟 peter 的 log 這個 function 是在做同一件事,但其實還是佔用了兩份空間,意思就是他們其實是兩個不同的 function。

如果我要兩個都是一樣那怎麼辦呢?我們可以把這個 function 抽出來,變成所有 Person 都可以共享的方法。這邊我們要用一個東西叫做prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.log = function () {
console.log(this.name + ', age:' + this.age);
}

var nick = new Person('nick', 18);
var peter = new Person('peter', 20);

console.log(nick.log === peter.log) // true

nick.log(); // nick, age:18
peter.log(); // peter, age:20

看到這裡我就有一個疑問,呼叫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
2
3
4
5
6
7
8
9
10
11
12
function Person(name, age) {
this.name = name;
this.age = age;
}

Person.prototype.log = function () {
console.log(this.name + ', age:' + this.age);
}

var nick = new Person('nick', 18);

console.log(nick.__proto__ === Person.prototype) // true

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');,那它有以下幾件事情要做:

  1. 創出一個新的 object,我們叫它 O
  2. 把 O 的 __proto__ 指向 Person 的 prototype,才能繼承原型鍊
  3. 拿 O 當作 context,呼叫 Person 這個建構函式
  4. 回傳 O

把所有的資料都看過一遍,但感覺還要再看一次還有些地方不是很了解,大家有興趣可以到看一下原作者所寫的,他還有附上超多參考資料。
github.com