# [IM010] This in JavaScript > **前言** > 2020 秋天,我將用 30 天的時間,來嘗試回答和網路前端開發相關的 30 個問題。30 天無法一網打盡浩瀚的前端知識,有些問題可能對有些讀者來說相對簡單,不過期待這趟旅程,能幫助自己、也幫助讀者打開不同的知識大門。有興趣的話,跟著我一起探索吧! > ![](https://images.unsplash.com/photo-1507652955-f3dcef5a3be5?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=2100&q=80) ## What's this? 在物件導向的程式語言當中常常回看到 this 的出現,通常這個 this 會指向物件本身,譬如 ```javascript= const obj = { value: 18, print: function () { console.log(this.value) } } obj.print() // 18 ``` 但是,好像不是每次使用 this 的時候都能如願以償,如果把上面的 print function 改成 arrow function,就沒辦法得到一樣的值 ```javascript= const obj = { value: 18, print: () => { console.log(this.value) } } obj.print() // undefined ``` 所以想趁這個機會,再次釐清一下在 JavaScript 當中關於 this 的用法。不過關於 this 的用法,網路上已經有許多的討論,特別是 Huli 的 [淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂](https://blog.huli.tw/2019/02/23/javascript-what-is-this/),內容相當的豐富。所以今天我想反過來,直接從規則和案例出發,幫助自己在實作的時候更能夠上手。 當然,還是建議大家有機會能閱讀相關文章當中的深入討論。 ## Rules 1. this 指向的值,跟作用域或在程式碼的哪個位置沒有關係,只跟如何被呼叫有關 2. 如果找不到被呼叫的對象,那麼 this 就會是 * 在嚴格模式下,this 為 undefined * 在非嚴格模式下,在瀏覽器當中,會是 window * 在非嚴格模式下,在 Node.js 當中,會是 global * 但是如果遇到 arrow function,則 this 會和「在哪裡被宣告」有關 ## Cases 以下的例子都是在瀏覽器中執行: ### 1. ```javascript= const obj = { value: 18, print: function () { console.log(this) } } let fn = obj.print console.log(fn) // [Function: printer] obj.print() // { value: 18, print: [Function: printer] } fn() // window ``` 在這裡我們建立了一個變數 `fn` 來指向`obj.print` 這個 function,看起來 `fn` 跟 `obj.print` 是一模一樣的 function,然而印出來的東西卻完全不一樣。 那是因為在執行 `obj.print()` 的時候,`print` function 知道他是被 `obj` 呼叫,因此當中的 this 會指向 `obj`。要怎麼知道誰呼叫誰呢?看那個 `.` 就對了!所以 `obj.print()` 的意思就是 `obj` 呼叫 `print` function 並執行 而當我們在執行 `fn()` 的時候,因為沒有那個 `.`,所以找不到可以指向的對象,在找不到的情況下,就會直接指向 `window`! *** ### 2. ```javascript= const obj = { value: 18, tool: { value: 81, print: function() {console.log(this.value)} } } let a = obj.tool let b = obj.tool.print obj.tool.print() // 81 a.print() // 81 b() // undefined ``` 根據同樣的邏輯,往前找是誰呼叫了這個 function,所以在 `obj.tool.print()` 這個例子,是 `tool` 呼叫了 `print`,所以值是 81。 `a.print()` 這個例子是 `a` 呼叫了 `print`,而 `a` 本身就是 `tool`,所以結果一樣是 81。 最後,雖然 `b` 本身就是 `print`,但是因為找不到呼叫的對象,所以 `this` 會指向 `window`,而因為 `window` 當中找不到 `value` 這個值,所以得到的結果是 undefined。 *** ### 3. ```javascript const obj = { value: 18, print: function () { function c () { console.log(this) } c() } } obj.print() // window ``` 在這個 case 當中,`print` function 裡面包含了另外一個 `c` function,而且這個 `c` 會直接執行。雖然看起來是因為 `print` 執行之後 `c` 跟著執行,所以好像是 `print` 呼叫了他。但回到剛剛的規則上,`c` 其實找不到是誰呼叫他的(前面沒有 `.`),所以在 `c` 當中的 `this` 就是 window。 如果今天要讓 `c` function 當中的 `this` 指向 `obj`,可以怎麼做呢? **解法 1: 先把 this 存下來** ```javascript= const obj = { value: 18, print: function () { let self = this function c () { console.log(self) } c() } } obj.print() // {value: 18, print: [Function]} ``` **解法 2: 變成 IIFE,直接把 this 給傳進去** ```javascript= const obj = { value: 18, print: function () { (function c (self) { console.log(self) })(this) } } obj.print() // {value: 18, print: [Function]} ``` **解法 3: 使用 arrow function** ```javascript= const obj = { value: 18, print: function () { const c = () => console.log(this) c() } } obj.print() // {value: 18, print: [Function]} ``` arrow function 的出現使得規則好像變得不太一樣了?沒錯,Arrow function 當中的 this,會跟「**它被宣告的地方**」有關,而不是跟「它被呼叫的對象」有關。所以因為這裡我們是在 `obj` 裡面宣告 `c`,所以 `c` 當中的 `this` 就會指向 `obj`。 **解法 4: 使用 call, apply, bind** ```javascript= // call const obj = { value: 18, print: function () { function c(){ console.log(this) } c.call(this) c() } } obj.print() // {value: 18, print: [Function]} ``` ```javascript= // apply const obj = { value: 18, print: function () { function c(){ console.log(this) } c.apply(this) } } obj.print() // {value: 18, print: [Function]} ``` ```javascript= // bind const obj = { value: 18, print: function () { function c(){ console.log(this) } const d = c.bind(this) d() } } obj.print() // {value: 18, print: [Function]} ``` 使用 JavaScript 當中的 `call`, `apply`, `bind` 方法,綁定 function 當中的 `this`。三者的差異在於: * `call`: 綁定 `this` 並可以傳入引數 * `apply`: 跟 `call` 一樣,只是用陣列的方式傳入引數 * `bind`: 永久綁定 `this`。使用的時候會產生新的 function,因此需要傳出來使用。或者當下直接執行。以剛剛的例子來說,就是 ```javascript= // 傳出來使用 const d = c.bind(this) d() // {value: 18, print: [Function]} // 直接執行 c.bind(this)() // {value: 18, print: [Function]} ``` ## End 希望看完以上的範例之後,下次看到 this 就不會再那麼害怕。不過就算當下不確定,還是可以隨時用 console.log 把 this 印出來看看。今天就簡單先到這邊,我們明天見囉! ### Ref * [淺談 JavaScript 頭號難題 this:絕對不完整,但保證好懂](https://blog.huli.tw/2019/02/23/javascript-what-is-this/) *** > **TD** > Be curious as astronomer, think as physicist, hack as engineer, fight as baseball player > [More about me](https://tsungtingdu.github.io/profile/) > > *"Life is like riding a bicycle. To keep your balance, you must keep moving."* *** ###### tags: `ironman` `javascript`