
結論先講
this 在 JavaScript 裡不是呼叫時的那個 function,而是根據呼叫方式動態決定。規則:
| 呼叫方式 | this 指向 |
|---|---|
獨立函式 fn() | undefined(嚴格)/ window(非嚴格) |
物件方法 obj.fn() | obj |
fn.call(x) / fn.apply(x) | x |
fn.bind(x) 後再呼叫 | x(永久綁定) |
new Fn() | 新建的物件 |
| 箭頭函式 | 繼承外層的 this(不是自己的) |
搞懂這 6 種情境就解決 99% 的 this bug。React class 跟 callback 是兩個最常翻車的場景。
為什麼 this 會出 bug
const user = {
name: 'A',
greet() {
console.log(this.name);
}
};
user.greet(); // "A" ✅
const fn = user.greet;
fn(); // undefined ❌同一個函式,呼叫方式不同 → this 不同。
看懂這個例子就 40% 理解 this 了。
四大綁定規則
規則 1:Default Binding(預設)
直接呼叫函式,沒任何前綴:
function foo() {
console.log(this);
}
foo();
// 嚴格模式:undefined
// 非嚴格模式:window(瀏覽器)或 global(Node)現代 ES modules 預設嚴格模式,所以 this 會是 undefined。
規則 2:Implicit Binding(隱式)
透過物件呼叫:
const obj = {
x: 10,
show() { console.log(this.x); }
};
obj.show(); // 10,this = obj關鍵:this 看呼叫時的 . 前面是誰,不是函式定義在哪。
隱式丟失
const show = obj.show;
show(); // undefined, this 是 default
setTimeout(obj.show, 1000); // 也是 default把 method「拔出來」後,this 丟失。這是最常踩的坑。
規則 3:Explicit Binding(顯式)
用 .call()、.apply()、.bind() 強制指定:
function greet() { console.log(this.name); }
const user = { name: 'Alice' };
greet.call(user); // "Alice"
greet.apply(user); // "Alice"(只差多參數傳法)
const bound = greet.bind(user);
bound(); // "Alice",永久綁定
bound.call({ name: 'Bob' }); // 仍是 "Alice",bind 鎖死了call vs apply vs bind
fn.call(thisArg, a, b, c); // 立即呼叫,傳參用逗號分開
fn.apply(thisArg, [a, b, c]); // 立即呼叫,傳參用陣列
fn.bind(thisArg, a, b); // 不呼叫,回傳新函式,已綁 thisArg + 部分參數口訣:Comma / Array / Bind。
規則 4:new Binding
function User(name) {
this.name = name;
}
const u = new User('Alice');
console.log(u.name); // "Alice"new 做的事:
- 建立空物件
- 把
this指向新物件 - 執行函式
- 回傳新物件(除非函式明確 return 其他物件)
這是 ES6 class 底下的真實運作。
箭頭函式:沒有自己的 this
箭頭函式不遵守上面四條規則。它繼承外層的 this。
const user = {
name: 'A',
greetArrow: () => console.log(this.name),
greetRegular() { console.log(this.name); }
};
user.greetArrow(); // undefined(箭頭繼承外層,外層是 module/global)
user.greetRegular(); // "A"為什麼發明箭頭函式
解決 callback 裡 this 丟失的老問題:
class Timer {
constructor() {
this.count = 0;
}
// ❌ 舊寫法
startOld() {
setInterval(function() {
this.count++; // 這個 this 不是 Timer!是 undefined/window
}, 1000);
}
// ✅ 箭頭函式繼承外層 this
startNew() {
setInterval(() => {
this.count++; // 這個 this 是 Timer instance
}, 1000);
}
}箭頭函式在 callback、map/filter/reduce 裡特別好用,不用一堆 bind(this)。
箭頭函式的限制
- 不能當 constructor(
new ArrowFn()噴錯) - 沒有
arguments物件(用 rest params...args取代) - 不能用
yield(不是 generator)
實戰場景:React Class Component
箭頭函式是 class handler 的標準寫法:
class Counter extends React.Component {
state = { count: 0 };
// ❌ 方法:this 會丟
incrementOld() {
this.setState({ count: this.state.count + 1 });
}
// ✅ 箭頭函式屬性:this 永遠是 instance
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<>
<button onClick={this.incrementOld}>Broken</button>
<button onClick={this.increment}>Works</button>
<button onClick={this.incrementOld.bind(this)}>Manual bind</button>
<button onClick={() => this.incrementOld()}>Inline arrow</button>
</>
);
}
}為什麼 onClick={this.incrementOld} 會壞
React 把 this.incrementOld 拿出來當 callback 傳。隱式綁定丟失,呼叫時 this 變 undefined。
四種修法比較
| 寫法 | 效能 | 可讀性 |
|---|---|---|
incrementOld = () => {} 箭頭屬性 | ✅ 一次綁定 | ✅ |
this.incrementOld.bind(this) | ❌ 每 render 新建函式 | ❌ |
() => this.incrementOld() inline | ❌ 每 render 新建函式 | ⚠️ |
Constructor 裡 this.x = this.x.bind(this) | ✅ 一次 | ❌ 囉嗦 |
推箭頭屬性。Hooks 年代基本沒這問題了,但面試/維護老 code 都會遇到。
實戰場景:事件監聽器
class Button {
constructor(el) {
this.el = el;
this.clicks = 0;
// this.handleClick = this.handleClick.bind(this); // ❌ 老做法
}
// ✅ 箭頭屬性
handleClick = () => {
this.clicks++;
};
attach() {
this.el.addEventListener('click', this.handleClick);
}
detach() {
this.el.removeEventListener('click', this.handleClick); // 要同一個 reference
}
}關鍵:attach 跟 detach 要同一個函式 reference。如果用 inline () => ...,detach 拿不到同一個 reference,不會真的解除監聽。
Debug this 的秘訣
1. 看「呼叫時的 .」
a.b.c.fn(); // this = cthis 永遠是最後一個 . 前面的那個物件(隱式綁定情況)。
2. Strict mode 救你
在 module 或 'use strict' 下,遺失 this 會直接噴錯而不是變 undefined 讓 bug 晚點爆。
3. TypeScript 型別系統
TS 4.3+ 有 this 型別,能在編譯期抓出很多 binding bug。
實戰 Checklist
- 預設用箭頭函式(省煩惱)
- 需要
this動態決定(如物件方法)→ 用 regular function -
bind/call/apply只在特殊情境用 - React class handler 用箭頭屬性
- Event listener 要 detach 時,保留同一個函式 reference
- 嚴格模式幫你抓 this 丟失
- 迷茫時,找呼叫點的
.前面是誰
相關文章
- Closure — 箭頭函式繼承 this,背後機制是 lexical scope
- JS 子 Roadmap
- Frontend Roadmap
