JavaScript 物件中的函式與 this

閱讀時間約 7 分鐘

JavaScript 物件可以放入字串、數字、布林值等 primitive data,同時也可以放入更多物件、陣列、函式等等。前述的靜態資料在物件當中稱為屬性 (property),而函示則稱為方法 (method)。

函示在 JavaScript 裡面通常會以匿名函式的樣態呈現,以下提供程式碼參考:

let user = {
name: "Mimiball",
age: 30,
greet: function () {
console.log("Hey!")
}
};

user.greet(); // Hello!

這樣運作下來其實和一般的函示沒有太大差異,但如果我們希望藉由物件中的函示,來存取其他同個物件當中的資料,就需要注意 this 關鍵字了。

透過 method 存取物件中其他屬性值

假設我們希望 greet 這個函式除了列印出純字串之外,也想帶入物件屬性 name 的值,可以參考以下寫法:

let user = {
name: "Mimiball",
age: 30,
greet: function () {
console.log(`Hey! This is ${user.name}`);
}
};

user.greet(); // Hey! This is Mimiball


我們透過 Template Literals 將 name 帶入到 console.log 裡面,而 name 既然是物件 user 的屬性,所以要用點記法 (dot notation) 來取出屬性的值。

嗯~事情看起來非常順利,但這樣的寫法其實不可靠。我們將狀況搞得複雜一些,把 user 「複製」給另一個變數 admin,然後再白目地賦值 null 給 user,最後呼叫 admin.greet()


let user = {
name: "Mimiball",
age: 30,
greet: function () {
console.log(`Hey! This is ${user.name}`);
}
};

let admin = user;
user = null;

admin.greet(); // TypeError: Cannot read property 'name' of null


Cannot read property 'name' of null... 但我明明是在 user 複製給 admin 之後才將 user 重新賦值 null 啊 🫠

抱著疑惑的心,我開始上網輸入 JavaScript object copy 等關鍵字才恍然大悟。JavaScript 的物件是 by reference

By value vs By reference

由於網路上有太多相關的精采文章了,這邊不再花篇幅紀錄。總而言之,JavaScript 的物件是 by reference,更嚴格來說似乎是 by sharing,但大家還是習慣以 by reference 稱呼之。

簡單來說,by reference 就是複製了 user 物件的記憶體位址 (memory address) 給 admin,所以 user 和 admin 其實都指向同一個物件。難怪 user 被覆寫成 null 之後,admin.greet() 會顯示無法讀取 null 的屬性。

既然無法用 user.name 來取值,那個怎麼辦呢?問題總是要解決吧?這個時候關鍵字 this 就派上用場了。

在 method 中使用關鍵字 this

this 關鍵字很特殊,它代表了當前執行程式碼的物件,所以在不同的情境之下,this 的值可能發生變化。由於我們這次遇到的問題和 method 有關,所以只先看函式中的 this

在函式中,this 的值取決於函式是如何被呼叫的。如果函數是作為物件的 method 被呼叫,那 this 就指向呼叫該 method 的物件。

更加白話文的版本:

this 的值是「.之前的物件」,也就是用來呼叫該方法的那個物件。

回到前面的問題,我們把程式碼改成 this.name

let user = {
name: "Mimiball",
age: 30,
greet: function () {
console.log(`Hey! This is ${this.name}`); // Modify this line of code
}
};

let admin = user;
user = null;

admin.greet(); // Hey! This is mimiball


媽媽咪呀 🤌 Perfecto! 問題解決了!

等等,可是這樣還是有點奇怪啊,admin 呼叫了 this,可是剛剛又說物件是 by reference,那就算改成 this.name,還是指向已經被覆寫為 null 的物件了不是嗎?這樣 user.name 改成 this.name 等於換湯不換藥唉......

腦中浮現了這樣的疑問,我才意識到自己根本沒搞懂前面 by reference 背後的記憶體分配概念,好在 Alpha Camp 助教以及 ChatGPT 的協助下,我終於看清楚了背後的原因。


用圖解了解背後到底發生了什麼事情

  1. 我們宣告了變數 user,JavaScript 變數起先的值都是 undefined,直到我們將物件腹值給 user,但由於 JavaScript 物件是 by reference,所以實際上儲存在變數 user 的值為物件的記憶體位址 0x0001
細體字為值;粗體字為記憶體位址

細體字為值;粗體字為記憶體位址

  1. 接著我們宣告第二個變數 admin,並將 user 「複製」給 admin。因為 user 這個變數實際上儲存的值為記憶體位址 0x0001,所以 admin 也存入了這個 reference。於是兩者同時指向物件。
raw-image
  1. user 被賦值 null,原先指向物件的箭頭更改。不過 admin 仍舊指向物件,所以函式改成 this.name 才會能夠正常運作!
raw-image

關於 JavaScript 物件的複製

其實問題裡面的 let admin = user 並不是很好的物件複製方式。從上面第二張圖可以發現,利用這樣將 a 物件賦值給 b 物件的手法,會造成之後無論透過誰來更改物件屬性都彼此影響,因為兩者指向的始終為同一個物件。

有鑑於此,我們能善用 spread operatorObject.assign() 等方法進行物件複製。值得留意的是,如果物件裡面有第二層物件或陣列 (陣列在 JavaScript 也是物件......),spread operator在複製第二層時,還是會回到 by reference。這似乎與淺拷貝 (shallow copy) 有關,這部分可以參考下方的文章連結~


Reference:

16會員
34內容數
Bonjour à tous,我本身是法文系畢業,這邊會刊登純文組學習網頁開發的筆記。如果能鼓勵更多文組夥伴一起學習,那就太開心了~
留言0
查看全部
發表第一個留言支持創作者!