# [IM010] This in JavaScript
> **前言**
> 2020 秋天,我將用 30 天的時間,來嘗試回答和網路前端開發相關的 30 個問題。30 天無法一網打盡浩瀚的前端知識,有些問題可能對有些讀者來說相對簡單,不過期待這趟旅程,能幫助自己、也幫助讀者打開不同的知識大門。有興趣的話,跟著我一起探索吧!
>

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