# Day 22：箭頭函數 (Arrow Functions) 的 this 和你想的不一樣 (2)

## 3. 顯性函數綁定 (Explicit Function Binding)

### 3.1. Function.prototype.bind() 篇

#### 傳統函數

`Function.prototype.bind()` 可以為一個函數建立新函數物件，新函數物件會繼承原函數的 prototype，同時任意綁定一個固定的擁有者。

因此以下例子中的 `introIronMan()` 和 `introCaptainAmerica()` 雖然呼叫形式上是簡單呼叫，`this` 會指向自己的綁定物件，而非指到 Global 物件。

```javascript
var getFullName = function() {
    return this.firstName + " " + this.lastName;
}

var firstName = "One", lastName = "Jar";
var introIronMan = getFullName.bind( { firstName: "Tony", lastName : "Stark" } );
var introCaptainAmerica = getFullName.bind( { firstName: "Steven", lastName : "Rogers" } );

console.log(getFullName());           // "One Jar"
console.log(introIronMan());          // "Tony Stark"
console.log(introCaptainAmerica());   // "Steven Rogers"
```

#### Arrow Functions

就像前面一再強調，**Arrow Functions 的 `this` 判斷看的是語彙位置**，因此 `Function.prototype.bind()` 的 Binding 不會發生作用，同樣只會沿用外層的 `this` 物件。

這個例子裡，`getFullName()` 往外一層是 Global Context，不管在一般模式或嚴謹模式，`this` 都是 Global 物件：

```javascript
var getFullName = () => {
    return this.firstName + " " + this.lastName;
}

var firstName = "One", lastName = "Jar";
var introIronMan = getFullName.bind( { firstName: "Tony", lastName : "Stark" } );
var introCaptainAmerica = getFullName.bind( { firstName: "Steven", lastName : "Rogers" } );

console.log(getFullName());           // "One Jar"
console.log(introIronMan());          // "One Jar"
console.log(introCaptainAmerica());   // "One Jar"
```

### 3.2. Function.prototype.apply() / Function.prototype.call() 篇

#### 傳統函數

透過 `apply()` / `call()` 執行某個函數物件，同時指定一個物件作為 `this`，然後回傳函數的執行結果：

```javascript
var whatsThis = function() {
    return this;
};
var getFullName = function() {
    return this.firstName + " " + this.lastName;
}

var ironMan = { firstName: "Tony", lastName : "Stark" };
var captainAmerica = { firstName: "Steven", lastName : "Rogers" };

console.log(whatsThis.apply(ironMan) === ironMan);                // true
console.log(getFullName.apply(ironMan));               // "Tony Stark"
console.log(whatsThis.apply(captainAmerica) === captainAmerica);  // true
console.log(getFullName.apply(captainAmerica));        // "Steven Rogers"
```

#### Arrow Functions

就像 `Function.prototype.bind()` 的 Binding 不會發生作用，`apply()` 和 `call()` 也同樣無效。只會依照語彙位置來判定 `this` 物件。

這個例子裡的 `getFullName()` 和 `whatsThis()` 往外一層都是 Global Context，因此不管在一般模式或嚴謹模式，`this` 都是 Global 物件，而 Global 物件裡並沒有 `firstName` 和 `lastName` 變數，所以印出 `"undefined undefined"`：

```javascript
var whatsThis = () => {
    return this;
};
var getFullName = () => {
    return this.firstName + " " + this.lastName;
}

var ironMan = { firstName: "Tony", lastName : "Stark" };
var captainAmerica = { firstName: "Steven", lastName : "Rogers" };

console.log(whatsThis.apply(ironMan) === window);        // true
console.log(getFullName.apply(ironMan));          // "undefined undefined"
console.log(whatsThis.apply(captainAmerica) === window); // true
console.log(getFullName.apply(captainAmerica));   // "undefined undefined"
```

## 4. 函數作為建構子

#### 傳統函數

將函數當作建構子，透過 new 關鍵字來產生一個物件，該物件會形成自己的環境 (Context)，原本函數內的 `this.xxx` 變成新物件的屬性。例如以下範例：

```javascript
var Hero = function(n){
    this.exp = n;
};

var h = new Hero(100);
console.log(h);         // Hero {exp: 100}
console.log(h.exp);     // 100
```

#### Arrow Functions

Arrow Function 所宣告的函數不能拿來當建構子，也不存在 `this` 的問題。

```javascript
var Hero = (n) => {
    this.exp = n;
};

var h = new Hero(100); // TypeError: Hero is not a constructor
```

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

### 5.1. 簡單呼叫 Callback Function

#### 傳統函數

我們會把某函數 A 當作參數傳入函數 B，函數 A 就是 Callback Function。

而傳統函數裡，Callback Function 裡的 `this` 是誰，視乎在函數 B 裡是怎麼呼叫函數 A。如果是最常見的「簡單呼叫」的形式，此時 `this` 在一般模式下就是 Global 物件，嚴謹模式則是 `undefined`：

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

var sayHi = function(){
  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
```

#### Arrow Functions I

當函數 A (Callback Function) 是傳統函數，不管函數 B 是傳統函數 (`hero.act1()`) 還是箭頭函數 (`hero.act2()`)，因為 Callback Function 本身是傳統函數，裡面的 `this` 比照傳統函數的判斷方式，也就是看呼叫方式。

由於都是透過簡單呼叫，所以 `this` 在一般模式下是 Global 物件，嚴謹模式是 `undefined`：

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

var sayHi = function(){
  return this.name;
}

var hero = {
  name: "Hi I am a Hero",
  act1: function(cbk){
    return cbk();
  },
  act2: (cbk) => {    // arrow function
    return cbk();
  }
};

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

#### Arrow Functions II

當函數 A (Callback Function) 是箭頭函數，不管函數 B 是哪一種函數，都是看 Callback Function 本身的語彙位置。

由於 `sayHi()` 沿用外層的 `this`，不管是一般模式或嚴謹模式，`this` 都是 Global 物件：

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

var sayHi = () => {    // arrow function
  return this.name;
}

var hero = {
  name: "Hi I am a Hero",
  act1: function(cbk){
    return cbk();
  },
  act2: (cbk) => {    // arrow function
    return cbk();
  }
};

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

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

#### 傳統函數

透過 `apply()` / `call()` 可以明確地控制函數裡的 `this` 物件是誰：

```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
```

#### Arrow Functions I

當函數 A (Callback Function) `sayHi()` 是傳統函數時，受 `apply()` 效果影響：

* 當函數 B 也是傳統函數 (`hero.act1()`) ：`hero.act1()` 自己的 `this` 看呼叫者是誰，也就是 `hero`，所以 `apply()` 將 `hero` 綁定為 Callback Function 的 `this`，因此印出 `"Hi I am a Hero"`。
* 當函數 B 是 Arrow Function (`hero.act2()`) ：`hero.act2()` 自己的 `this` 沿用外層，也就是 Global 物件 (無論一般模式或嚴謹模式)；再透過 `apply()` 將 Global 物件綁定於 `sayHi()` 的 `this`，因此印出 `"Hi I am Global"`。

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

var sayHi = function(){
  return this.name;
}

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

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

#### Arrow Functions II

由於 `apply()` / `call()` 的綁定效果對 Arrow Function 無效，如果函數 A (Callback Function) `sayHi()` 是 Arrow Function，無論函數 B 是傳統函數 (`hero.act1()`) 或者 Arrow Function (`hero.act2()`)，`sayHi()` 裡的 `this` 都是沿用外層，也就是 Global 物件 (無論一般模式或嚴謹模式)：

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

var sayHi = () => {    // arrow function
  return this.name;
}

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

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

> 可以注意到，同樣遇到 Callback Functions 的情境，傳統函數和箭頭函數的判斷原理可說是完全相反：
>
> * 傳統函數：看的是函數 B，也就是呼叫方怎麼呼叫函數 A。
> * 箭頭函數：看的是函數 A，也就是函數自身定義的語彙位置。

## 總結

### 一句話總結傳統函數和箭頭函數在 `this` 判斷上的差別

* 傳統函數：看呼叫時的物件是誰。
* 箭頭函數：看函數本身定義的語彙位置。

### 既然一句話就能講完，吃飽太閒看這麼多範例？

這兩篇文章不厭其煩的將一些情境的範例用 Arrow Function 走過一遍，十分囉嗦。

![](https://i.imgur.com/mxGyGFc.png)\
(Source: [Youtube](https://www.youtube.com/watch?v=wPxXT0nB36I))

其實目的是希望**能不犯了「想當然耳」的毛病**。

前面一大堆情境程式碼，很多情境的差別可能很細微，之所以不厭其煩逐一走過，其實就像一種交叉驗證，反覆驗證歸納出來的規則是否有漏洞，確認真的能夠解釋每個範例的背後原理。

**在理解觀念時，很容易在腦海裡模模糊糊以為自己都懂了，殊不知有些細節並沒有真正理解清楚**。

事實上在寫這個系列文之前，很多觀念我以為我早就熟稔。結果將腦海中的理解化為具體文章的過程，才發現自己在一些細節並不如自以為的了解。

在前面介紹各種範例的過程可以發現，很多範例的 `this` 最後呈現結果雖然一樣，可能都是 Global 物件，但背後形成「`this` 是 Global 物件」的原因並不相同。

有時程式碼 99% 一樣，僅僅是定義時用傳統函數或箭頭函數的差別，就可以讓結果天差地遠。

**如果對於背後細節原理不加分辨，未來一旦換了不同的程式碼情境，很可能就判斷錯誤**。

就像看人家講解程式題目，常常覺得聽起來很容易，以為自己都懂了，一旦輪到自己上戰場，才發現很多細節並沒有理解清楚，換個情境就卡關。

要確保自己的理解沒有死角，最好的方法就是實地走過一次。

事實上可能遇到的程式情境太多了，有太多令人混淆之極的例子可以研究。

不厭其煩地對各種情境動手實作，就是學好程式的不二法門！

## References

* [W3Schools - ECMAScript 6 - JavaScript 6](https://www.w3schools.com/js/js_es6.asp)
* [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/)
* [\[ES6\] Javascript 開發者必須知道的 10 個新功能](https://medium.com/@peterchang_82818/es6-10-features-javascript-developer-must-know-98b9782bef44)
* [ES6,ES7,ES8 · class - easonwang01 - GitBook](https://easonwang01.gitbooks.io/class/es6es7.html)


---

# 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/day22.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.
