分析Js中的 this 指向筆記
Created: 2025/1/12
Updated: 2024/8/2
理解並分析關於js中this的部分,並理解如何指向的規則
1. 理解 JavaScript 中的 this
在 JavaScript 中,
this
是一個經常引起困惑的概念。在JavaScript 中的
this
值在函數執行時動態確定,其指向取決於函數的調用方式,也就是指向函數執行時的上下文對象(Function Execution Context)。
重要原則:this
的值與函數的定義位置無關,只與調用方式有關。
this的綁定規則
我們要記住最高原則,就是誰呼叫執行的時候,就指向誰
根據這個原則,this
的綁定規則可以大致分為以下 5 種:
預設綁定、隱含綁定、顯式綁定、new 綁定以及箭頭函式綁定
1.1 預設綁定(Default Binding)
直接調用函式時的預設行為
- 非嚴格模式:
this
指向全域物件(瀏覽器中為window
) - 嚴格模式:
this
為undefined
function showThis() {
console.log(this);
}
showThis(); // 非嚴格模式:window,嚴格模式:undefined
1.2 隱含綁定(Implicit Binding)
作為物件方法調用時,this
指向調用該方法的物件
const obj = {
name: "MyObject",
sayName: function() {
console.log(this.name); // this 指向 obj
}
};
obj.sayName(); // 輸出:"MyObject"
// 注意常見問題
const fn = obj.sayName;
fn(); // 輸出:undefined(this 指向全域物件)
1.3 顯式綁定(Explicit Binding)
使用 call()
、apply()
、bind()
明確指定 this
的值
function greet() {
console.log(`Hello, ${this.name}!`);
}
const person = { name: "Alice" };
greet.call(person); // 輸出:"Hello, Alice!"
1.4 new 綁定(new Binding)
使用 new
關鍵字調用函式時,this
指向新建立的物件實例:
並且會執行steps來自動綁定
- 創建一個新的空object
- 將該object的prototype設為構造函式的 prototype 屬性
- 將函式內的 this 綁定到新建立的object
- 執行構造函式中的代碼,並且當該函式沒有返回其他物件,則返回這個新建立的物件
function Person(name) {
this.name = name; // this 指向新創建的物件
}
const person = new Person("Bob");
console.log(person.name); // 輸出:"Bob"
1.5 箭頭函式綁定(Arrow Function Binding)
箭頭函式是一個特別的函式,他沒有自己的 this
,會繼承外層作用域的 this
值
const obj = {
name: "MyObject",
regularFunction: function() {
console.log(this.name); // this 指向 obj
const arrowFunction = () => {
console.log(this.name); // this 繼承自 regularFunction,仍指向 obj
};
arrowFunction();
},
outerArrowFunction: () => {
console.log(this.name) // this指向最外層
}
};
obj.regularFunction();
// 輸出:
// "MyObject"
// "MyObject"
obj.outerArrowFunction();
// undefined
2. 綁定優先級
當多個規則同時適用時,優先級從高到低為:
- new 綁定 > 顯式綁定 > 隱含綁定 > 預設綁定
- 箭頭函式 不受以上規則影響,永遠使用詞法作用域的
this
function Testing(name) {
this.name = name;
this.value = 100; // 給新物件設定屬性
console.log('this.value:', this.value);
console.log('this.name:', this.name);
console.log('this 指向:', this.constructor.name);
return this.value; // 返回具體值
}
const obj1 = { value: 1, name: "Original" };
const boundTest = Testing.bind(obj1);
// 直接調用:顯式綁定生效
console.log("=== 直接調用 ===");
const result1 = boundTest("Alice"); // 修改 obj1
console.log("返回值:", result1); // 100
console.log("obj1 現在:", obj1); // { value: 100, name: "Alice" }
// new 調用:new 綁定優先
console.log("=== new 調用 ===");
const instance = new boundTest("Bob"); // 創建新物件
console.log("新物件:", instance); // Testing { name: "Bob", value: 100 }
console.log("obj1 不變:", obj1); // 仍然是 { value: 100, name: "Alice" }
3. 顯式綁定詳解
3.1 基礎範例
const obj = { num: 1 };
function add(a, b) {
return this.num + a + b;
}
3.2 call() 方法
立即執行函數,第一個參數為 this
值,後續參數逐一傳遞
console.log(add.call(obj, 2, 3)); // 輸出:6
// 等同於:obj.add(2, 3)
3.3 apply() 方法
立即執行函數,第一個參數為 this
值,第二個參數為參數陣列
console.log(add.apply(obj, [2, 3])); // 輸出:6
// 參數以陣列形式傳遞
3.4 bind() 方法
返回新函數,新函數的 this
永久綁定到指定物件
const boundAdd = add.bind(obj);
console.log(boundAdd(2, 3)); // 輸出:6
// bind 綁定是永久的,無法被二次綁定覆蓋
const doubleBound = boundAdd.bind({ num: 10 });
console.log(doubleBound(2, 3)); // 輸出:6(仍然是第一次綁定的結果)
4. 特殊場景與陷阱
4.1 回調函數中的 this 丟失
class Timer {
constructor() {
this.seconds = 0;
}
start() {
// 錯誤寫法:this 會丟失
setInterval(function() {
this.seconds++; // this 指向全域物件或 undefined
console.log(this.seconds);
}, 1000);
// 正確寫法 1:使用箭頭函數
setInterval(() => {
this.seconds++; // this 指向 Timer 實例
console.log(this.seconds);
}, 1000);
// 正確寫法 2:使用 bind
setInterval(function() {
this.seconds++;
console.log(this.seconds);
}.bind(this), 1000);
}
}
4.2 事件處理器中的 this
class Button {
constructor(element) {
this.element = element;
this.clickCount = 0;
// 傳統事件處理:this 指向 DOM 元素
this.element.addEventListener('click', function() {
console.log(this); // DOM 元素
});
// 箭頭函數:this 指向 Button 實例
this.element.addEventListener('click', () => {
this.clickCount++;
console.log(`Clicked ${this.clickCount} times`);
});
}
}
4.3 物件方法的賦值
const obj = {
name: 'Object',
getName: function() {
return this.name;
}
};
const getName = obj.getName;
console.log(getName()); // undefined(this 丟失)
// 解決方案
const boundGetName = obj.getName.bind(obj);
console.log(boundGetName()); // "Object"
5. 實際應用場景
5.1 函數柯里化(Currying)
function multiply(x, y) {
return x * y;
}
// 創建專用的乘法函數
const multiplyByTwo = multiply.bind(null, 2);
console.log(multiplyByTwo(5)); // 輸出:10
5.2 借用方法(Method Borrowing)
const arrayLike = { 0: 'a', 1: 'b', 2: 'c', length: 3 };
// 借用陣列的 slice 方法
const arr = Array.prototype.slice.call(arrayLike);
console.log(arr); // ['a', 'b', 'c']
// 或使用 apply
const arr2 = Array.prototype.slice.apply(arrayLike);
5.3 類別繼承中的 this
class Animal {
constructor(name) {
this.name = name;
}
speak() {
console.log(`${this.name} makes a sound`);
}
}
class Dog extends Animal {
speak() {
super.speak(); // this 仍指向 Dog 實例
console.log(`${this.name} barks`);
}
}
6. 比較
6.1 顯式綁定方法比較
方法 | 是否立即執行 | 參數傳遞方式 | 返回值 | 使用場景 |
---|---|---|---|---|
call() | 是 | 逗號分隔的參數列表 | 函數執行結果 | 立即調用,參數較少 |
apply() | 是 | 參數陣列 | 函數執行結果 | 立即調用,參數為陣列 |
bind() | 否 | 部分參數可預設 | 新函數 | 創建綁定函數,稍後調用 |
6.2 普通函數 vs 箭頭函數
特性 | 普通函數 | 箭頭函數 |
---|---|---|
this 綁定時機 | 調用時決定(動態) | 定義時決定(靜態) |
this 是否可改變 | 可用 call /apply /bind | 無法改變 |
適用綁定規則 | 全部五種規則 | 僅箭頭函數規則 |
可否作為構造函數 | 可以 | 不可以 |
arguments 物件 | 有 | 無 |
結論
- 在默認情況下,
this
的指向取決於函數的調用方式。 call
和apply
在調用時立即改變this
指向,並執行函數。bind
創建一個新函數,新函數的this
永久綁定到指定對象,原函數不受影響。bind
創建的新函數可以稍後調用,而call
和apply
是即時調用。