# JavaScript 中的 this
## 🔹 this 是什麼?
`this` 是 JavaScript 中的一個關鍵字,它的值通常根據呼叫方式決定。箭頭函式例外,它在定義時就繼承外層的 this。
## 判斷 this 的 簡易版
- 箭頭函式 → 定義時繼承外層 this
- 普通函式 → 呼叫時決定 this
- 物件呼叫 ➔ 指向物件
- 直接呼叫 ➔ window / undefined(看嚴格模式)
## 🧭 this 的指向規則總覽
| 呼叫方式/情境 | this 指向(在瀏覽器中) | 備註 |
|-------------------------------|------------------------------------------|-------------------------------------|
| 全域(非嚴格模式) | `window` | Node.js 中為 `global` |
| 全域(嚴格模式) | `undefined` | |
| 函式直接呼叫 | `window`(非嚴格) / `undefined`(嚴格) | |
| 物件方法呼叫 | 呼叫該方法的物件 | 例如 `obj.method()` |
| constructor 中 (`new`) | 新建立的實體物件 | `this` 指向 `new` 出來的物件 |
| class 中的 method | 該 class 實體 | 類似一般物件的行為 |
| callback 函式(一般函式) | `window`(非嚴格) / `undefined`(嚴格) | 例如:`[1,2,3].forEach(function(){})` |
| setTimeout/setInterval | `window` | Node.js 中為 `Timeout`(但非預期) |
|IIFE(立即執行函式)| `window`(非嚴格) / `undefined`(嚴格)
---
## 箭頭函式 this 行為
>箭頭函式永遠不會自己創造 this,它只會繼承「定義當下」的外層 this
## 箭頭函式的 `this` 行為總表
| 定義位置 / 外層 `this` | 箭頭函式的 `this` 指向(在瀏覽器中) | 備註 |
|-----------------------------------------------------|----------------------------------|--------------------------------------------------------------|
| **全域** | `window` | 若在全域中定義箭頭函式 |
| **class 中的 method** | 該 class 實體 | `this` 繼承外層 class context,例如 constructor 中定義箭頭函式 |
| **setTimeout / setInterval 中定義** | 外層 `this` | 例如在物件內使用箭頭函式當定時器 callback,會指向物件本身 |
| **callback 函式** | 外層 `this` | 常見於 class 或物件中使用 forEach 來處理資料時 |
| **IIFE(立即執行函式)** | 外層 `this` | 箭頭函式沒有自己的作用域,直接吃外面那層 |
---
> 🧠 **箭頭函式的 this 核心原則:**
> 箭頭函式永遠不會自己創造 `this`,它只會繼承「定義當下」的外層 `this`。
# this 的指向例子
## 🔸 1. 全域環境下的 this
```javascript
var myName = "小明";
console.log(this === window); // true(在瀏覽器中)
console.log(this.myName); // "小明"
```
## 🔸 2. 嚴格模式下的 this
```javascript
"use strict";
var myName = "小明";
function fn() {
console.log(this); // undefined
}
fn();
```
## 🔸 3. 純函式呼叫(非物件)
```javascript
var myName = "小明";
function sayName() {
console.log(this); // window(非嚴格)
console.log(this.myName); // "小明"
}
sayName();
```
## 🔸 4. 立即執行函式(IIFE)
```javascript
var myName = "小明";
(function () {
console.log(this); // window(非嚴格)
})();
(() => {
console.log(this); // window,箭頭函式繼承外層的 this
})();
```
## 🔸 5. 作為物件方法呼叫
```javascript
const family = {
myName: "小明家",
callName() {
console.log(this.myName); // "小明家"
},
};
family.callName();
```
## 🔸 6. 物件方法呼叫
```javascript
const family = {
myName: "小明家",
ming: {
myName: "小明",
callName() {
console.log(this.myName); // "小明"
},
},
};
family.ming.callName();
```
## 🔸 7. class 中的 this 及constructor 中 (new)
```javascript
class Person {
constructor(name) {
this.name = name;
console.log(this);// this 指向 new 出來的物件
}
sayHi() {
console.log("Hi, I am", this.name); // this 指向呼叫 sayHi() 的實例物件
}
}
const p1 = new Person("小明");
p1.sayHi(); // this === p1 → "Hi, I am 小明"
```
## 🔸 8. callback 中的 this(普通函式)
```javascript
[1, 2, 3].forEach(function () {
console.log(this); // window(非嚴格)
});
```
- 嚴格模式下
```javascript
"use strict";
const arr = [1, 2, 3];
arr.forEach(function (item) {
console.log(this); // ❗️在嚴格模式下,這裡的 this 是 undefined
});
```
## 🔸 9. setTimeout 中的 this
🔹 普通函式寫法(需要保存 this)
```javascript
const component = {
text: "Hello",
getData() {
const that = this;
setTimeout(function () {
that.text = "Hi";
console.log(that.text); // "Hi"
});
},
};
component.getData();
```
🔹 箭頭函式寫法(自動繼承外層 this)
```javascript
const person = {
name: "小明",
sayHi() {
setTimeout(() => {
console.log(this.name); // "小明"
}, 1000);
},
};
person.sayHi();
```
## 🔸 10. 箭頭函式的 this(沒有自己的 this)
```javascript
window.myName = "全域的小明";
const family = {
myName: "小明家",
callName: () => {
console.log(this.myName); // "全域的小明"(繼承外層的 this = window)
},
};
family.callName();
```
## 🔸 11. 箭頭函式 + 一般方法包住
```javascript
window.myName = "全域的小明";
const family = {
myName: "小明家",
callName: function () {
(() => {
console.log(this.myName); // "小明家"
})();
},
};
family.callName();
```
## 🔸 12. 展開運算子 (...) 與 this 的共享
- ...component 是淺拷貝
- getData/render/init 等 function 共用同一份參考
```javascript
const component = {
text: "預設文字",
el: document.getElementById("root"),
getData() {
const that = this;
setTimeout(function () {
that.text = "已更新";
that.render();
});
},
init() {
this.getData();
},
render() {
this.el.innerText = this.text;
},
};
const component2 = {
...component,
el: document.getElementById("root2"),
};
component.init();
component2.init();
```