分析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
  • 嚴格模式:thisundefined
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. 綁定優先級

當多個規則同時適用時,優先級從高到低為:

  1. new 綁定 > 顯式綁定 > 隱含綁定 > 預設綁定
  2. 箭頭函式 不受以上規則影響,永遠使用詞法作用域的 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 物件

結論

  1. 在默認情況下,this 的指向取決於函數的調用方式。
  2. callapply 在調用時立即改變 this 指向,並執行函數。
  3. bind 創建一個新函數,新函數的 this 永久綁定到指定對象,原函數不受影響。
  4. bind 創建的新函數可以稍後調用,而 callapply 是即時調用。