2021年5月8日 星期六

new 操作子 | JavaScript | 克服 JS 的奇怪部分

Photo by Michael Aleo on Unsplash

前一篇文章中,我們談到了 Javascript 的原型鏈的概念,了解了原型鏈是 Javascript 中實現繼承的方式。而今天我們利用原型鏈的概念,來看看 new 操作子到底是什麼東東。

先來看個例子吧


function Person(firstname, lastname) {
    this.firstname = firstname;
    this.lastname = lastname;
}

Person.prototype.getFullName = function() {
    console.log(this.firstname + ' ' + this.lastname);
};

var obj_p = new Person("Jeremy", "Lin");
obj_p.getFullName();

我們先來看看倒數第兩行,熟悉 Java 的小伙伴可能會覺得很親切。因為這就像是存在一個 Person 的類別,然後使用 new 建立了一個 Person 的物件。

然而,這在 Javascript 中,卻是另一個故事。

在 Javascript 中本質上並不存在 class 的概念(ES6 的 class 也只是語法糖而已),這邊的 Person 其實是一個 function。而 Javascript 中的 function 其實也是一種物件,這種物件帶有一個特殊的屬性叫 prototype。


prototype 這個屬性在平常的時候其實沒什麼作用。然而,如果在呼叫 function 前加上一個 new 操作子,則這時候 prototype 就開始發揮它的功能了。 通常當看到 new 操作子出現時,Javascript 引擎會做下面幾件事
  1. 建立一個空的物件
  2. 將這個空物件的 __proto__ 指向 function 的 prototype
  3. 呼叫 function ,並將這個空物件當做這個 function 的 this
  4. 看看 function 的回傳值是否是一個物件,如果是,則回傳這個物件。如果不是,則回傳步驟 1 所建立的物件。
是不是有點暈頭轉向了呢?
沒關係,我們用剛剛的例子來說明這一切!

當執行了 new Person("Jeremy", "Lin"); 時,會先建立一個空的物件(我們姑且將這個物件叫做物件P),且物件P的 __proto__ 會指到 Person 的 prototype。緊接著,呼叫 Person 這個 function,這裡就會將 this 指到剛剛所建立的物件P。因此,在執行完 Person 之後,物件P會有兩個屬性,一個是 firstname ,它的值為 "Jeremy"。另一個是 lastname ,它的值為 "Lin"。最後,由於 Person 並沒有回傳值,因此執行完 new 操作子後會回傳物件P,並將此結果賦予 obj_p。

小伙伴們可能會接著問,那當執行到 obj_p.getFullName();,又發生了什麼事呢?

我們先將目光轉移到中間那段程式碼

Person.prototype.getFullName = function() {
    console.log(this.firstname + ' ' + this.lastname);
};

這裡我們可以看到,Person 其 prototype 的 getFullName 被設定成一個 function。而因為剛剛提到,物件P的 __proto__ 會指到 Person 的 prototype。因此,當執行 obj_p.getFullName();時,透過了原型鏈,其實就會找到 Person 其 prototype 的 getFullName。

也就是說,我們最後應該會看到以下結果
> Jeremy Lin

沒有留言:

張貼留言