# Day 18：this 關鍵字 (4)

## 函數執行環境下 (Function Context) (續)

### 7. 回呼函數 (Callback Function) 裡的 this

> `this` 物件：視乎怎麼呼叫 Callback Function。

JavaScript 裡常會需要用到 Callback Function，將某個函數物件 A 當作參數傳進另一個函數 B，由函數 B 決定執行 A 的時機。

這時候就要注意函數 B 是如何去呼叫函數 A，否則函數 A —— 也就是 Callback Function —— 裡面的 `this`，很可能不是你所預期的對象。

#### 7.1. 簡單呼叫 Callback Function (一般模式)

今天我有一個 `hero` 物件，存在一些屬性，例如 `name`，我想透過 Callback 的方式去控制 `hero` 物件，我只在 `hero` 物件裡實作一個 `act()`，負責執行 Callback Function。而 Callback Function 的內容由其他人提供。例如以下：

```javascript
var name = "Hi I am Global";

function sayHi(){
  return this.name;
}

var hero = {
  name: "Hi I am a Hero",
  act: function(cbk){
    return cbk();
  }
};

console.log( sayHi() );           // Hi I am Global
console.log( hero.act(sayHi) );   // Hi I am Global
```

* `hero.act(sayHi)` 就是一個 callback 的用法，`sayHi` 就是 callback function。
* 在 `hero.act()` 裡，採用**簡單呼叫的方式**來執行 callback function。
* 根據昨天介紹的「 2.簡易呼叫 (Simple Call) 」，函數裡的 `this` 會是 Global 物件，所以 `hero.act(sayHi)` 回傳的結果是 Global 變數的 `name`。

#### 7.2. 簡單呼叫 Callback Function (嚴謹模式)

```javascript
"use strict";

var name = "Hi I am Global";

function sayHi(){
  return this.name;
}

var hero = {
  name: "Hi I am a Hero",
  act: function(cbk){
    return cbk();
  }
};

console.log( sayHi() );           // TypeError: Cannot read property 'name' of undefined
console.log( hero.act(sayHi) );   // TypeError: Cannot read property 'name' of undefined
```

* 如果在嚴謹模式下，透過簡單呼叫，函數裡 `this` 會是 `undefined`，無法執行 `this.name`，會發生錯誤。

但上述的範例，我想達到的效果是 `hero.act(sayHi)` 可以回傳物件 `hero` 自己的 `name` 值，我該怎麼做？

#### 7.3. 用 apply() / call() 將物件本身傳入 Callback Function

如果想把物件本身帶入 Callback Function 裡的 `this`，就要用 `apply()` / `call()`。

```javascript
var name = "Hi I am Global";

function sayHi(){
  return this.name;
}

var hero = {
  name: "Hi I am a Hero",
  act: function(cbk){
    return cbk.apply(this); // 將物件本身傳入 Callback Function
  }
};

console.log( sayHi() );           // Hi I am Global
console.log( hero.act(sayHi) );   // Hi I am a Hero
```

## 總結 this

### 要判斷 this 是誰，就看是誰呼叫

`this` 出現的位置有可能在：

* 全域執行環境下 (Global Context)
* 函數執行環境下 (Function Context)

但全域執行環境下 (Global Context) 遇到的機率相對低，而且十分單純，`this` 就是 Global 物件本身。

比較容易發生問題的是函數執行環境 (Function Context)，遇到的情境可能千變萬化，過程可能讓人很混淆。

記得一個大原則：**看呼叫時的物件是誰**。

**JavaScript 的 `this` 不是看定義的語彙位置，而是根據執行當下誰擁有這段程式碼，也就是看誰呼叫的**。

但要注意的是，簡單呼叫 (Simple Call) 的情形下，一般模式和嚴謹模式會有不同的行為。

### 函數執行環境下，判斷 this 的公式秘笈

所有人都知道，寫程式就像學數學一樣，不建議背誦，而是去理解背後原理 (話說回來，比樂透可能性還多的語法組合，企圖用背的也很不科學)。

但當你確定你已經理解原理之後，我不反對用一些類似口訣或公式筆記的方式來輔助記憶，幫助快速回憶。畢竟如果每次寫程式要用到時，都從頭推導，那效率是不現實的。

曾有人做過實驗，讓大學數學教授和高中生一起做同一份高中數學考券，結果高中生輕輕鬆鬆在時間內完成，數學教授卻沒有寫完。是因為數學教授的程度比高中生差嗎？

當然不是。

數學教授每一個題目都知道背後原理，給他足夠的時間推導，他每一題都能完美解答。但考試時間是有限的，對那些高中生來說，這些題目範圍是他們非常熟悉，幾乎看到題目腦海就浮現解法。這代表那些高中生不理解背後原理嗎？不，他們也是經過理解的過程學到這些題目的解法，但為了應付嚴峻的考試，他們用各種方法來加速遇到題目的解決速度，例如解題口訣、公式表。

現實中的專案開發也有同樣情境。專案開發通常有時程限制，為了減少每次回憶的時間，相信很多工程師都有自己的私藏筆記，這些筆記就是你理解的精華，用來幫助自己快速回想，或減少從頭推導的時間。

前面舉了非常多情境，整體歸納下來，大致上不出以下公式的範圍：

| 呼叫方式                | 模式   | this 所指的物件                                |
| ------------------- | ---- | ----------------------------------------- |
| obj.method()        | 不限   | 該 obj 物件                                  |
| function()          | 一般模式 | Global 物件                                 |
| function()          | 嚴謹模式 | `undefined`                               |
| 透過 apply() 或 call() | 不限   | 第一個參數的物件 (若第一個參數是 `null`，則視同「function()」) |

* 以上指的都是未經 `bind()` 綁定而來的函數物件
* 透過 `bind()` 產生的函數，不管呼叫方式為何，`this` 都指向當初 `bind()` 所綁定的物件。

### 小補充：`this` 不是變數

這一點我想大家都清楚，只是作為 `this` 介紹完整性的一個小補充。

正如最一開始所說，`this` 是一個關鍵字，不是變數，所以不能改變 this 的值，例如企圖這樣：

```javascript
this = { name: "OneJar" };
```

## References

* [W3Schools - The JavaScript this Keyword](https://www.w3schools.com/js/js_this.asp)
* [this - JavaScript | MDN](https://developer.mozilla.org/zh-TW/docs/Web/JavaScript/Reference/Operators/this)
* [#Javascript：this用法整理 | 英特尔® 软件](https://software.intel.com/zh-cn/blogs/2013/10/09/javascript-this)
* [JavaScript 語言核心（11）this 是什麼？ by caterpillar | CodeData](https://www.codedata.com.tw/javascript/essential-javascript-11-what-is-this/)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://something-about-js-book.onejar99.com/day18.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
