JS 中 this 關鍵詞的使用解析

在 JavaScript 中,this是一個極具辨識度又容易讓人混淆的關鍵詞。它的指向並非固定不變,而是取決於函數的調用方式,這一特性讓它在不同場景下表現出截然不同的行為。掌握this的綁定規則,是寫出健壯 JS 代碼的關鍵,尤其是在面向對象編程、事件處理和異步操作中,this的正確運用直接影響代碼邏輯的正確性。

一、全局作用域中的 this

在全局作用域下,this直接指向全局對象。瀏覽器環境中全局對象是window,Node.js 環境中則是global。

// 瀏覽器環境下測試
console.log(this === window); // 輸出:true

this.globalVar = "我是全局變量";
console.log(window.globalVar); // 輸出:我是全局變量
console.log(globalVar); // 輸出:我是全局變量(全局變量掛載在window上)

需要注意的是,在嚴格模式('use strict')下,全局作用域的this依然指向全局對象,但函數內的this會有不同表現。

二、函數調用中的 this

函數的調用方式是決定this指向的核心因素,不同調用方式對應不同的綁定規則。

1. 普通函數調用:this 指向全局對象

當函數以普通方式調用時,this默認綁定到全局對象。嚴格模式下,這種調用方式的this會變成undefined。

function normalFunc() {
  console.log(this); 
}

function strictFunc() {
  'use strict';
  console.log(this); 
}

normalFunc(); // 瀏覽器中輸出window,Node中輸出global
strictFunc(); // 輸出undefined
2. 對象方法調用:this 指向調用對象

當函數作為對象的方法被調用時,this會隱式綁定到調用這個方法的對象上。

const user = {
  name: "張三",
  age: 28,
  sayHello: function() {
    console.log(`我是${this.name},今年${this.age}歲`);
  }
};

user.sayHello(); // 輸出:我是張三,今年28歲

// 方法賦值給變量後,變成普通函數調用
const func = user.sayHello;
func(); // 瀏覽器中輸出:我是undefined,今年undefined歲(this指向window)
3. 構造函數調用:this 指向實例對象

當函數通過new關鍵字作為構造函數調用時,this會綁定到新創建的實例對象上。

function Person(name, age) {
  this.name = name;
  this.age = age;
  this.sayHi = function() {
    console.log(`Hi, ${this.name}`);
  };
}

const person1 = new Person("李四", 30);
person1.sayHi(); // 輸出:Hi, 李四
console.log(person1.name); // 輸出:李四

構造函數的核心就是利用this給實例對象掛載屬性和方法。

4. 箭頭函數調用:this 繼承自外層作用域

箭頭函數沒有自己的this綁定,它的this繼承自外層最近的非箭頭函數的作用域。這一特性完美解決了傳統函數在異步回調、事件處理中的this指向問題。

const obj = {
  name: "箭頭函數測試",
  func: function() {
    // 普通函數的this指向obj
    setTimeout(() => {
      // 箭頭函數繼承外層func的this
      console.log(this.name); 
    }, 100);
  },
  badFunc: function() {
    setTimeout(function() {
      // 普通函數this指向window
      console.log(this.name); 
    }, 100);
  }
};

obj.func(); // 輸出:箭頭函數測試
obj.badFunc(); // 瀏覽器中輸出:空字符串(window.name默認為空)

注意:箭頭函數不能作為構造函數,使用new調用會報錯。

三、手動改變 this 指向的方法

JS 提供了三個內置方法,可以手動指定函數的this指向,分別是call()、apply()和bind()。

1. call ():參數逐個傳入

call()方法會立即執行函數,第一個參數是this的指向,後續參數是函數的入參,逐個傳入。

function calculate(a, b) {
  return this.base + a + b;
}

const context = { base: 10 };
// this指向context,參數a=2,b=3
const result = calculate.call(context, 2, 3);
console.log(result); // 輸出:15
2. apply ():參數以數組形式傳入

apply()和call()功能一致,區別在於函數參數需要以數組的形式傳入。

// 複用上面的calculate函數
const result2 = calculate.apply(context, [4, 5]);
console.log(result2); // 輸出:19
3. bind ():返回綁定 this 的新函數

bind()不會立即執行函數,而是返回一個永久綁定了 this 指向的新函數,後續調用新函數時,this 不會改變。

// 複用上面的calculate函數
const boundFunc = calculate.bind(context);
// 調用新函數,參數逐個傳入
console.log(boundFunc(6, 7)); // 輸出:23

// bind可以預設部分參數
const partialFunc = calculate.bind(context, 8);
console.log(partialFunc(9)); // 輸出:27

四、實戰中的 this 常見坑點與解決

  1. 事件處理函數中的 this

瀏覽器事件處理函數中,this默認指向觸發事件的 DOM 元素,但在嵌套函數中容易丟失。

const btn = document.getElementById("test-btn");
btn.addEventListener("click", function() {
  console.log(this); // 指向btn元素
  setTimeout(function() {
    console.log(this); // 指向window
  }, 100);
});
// 解決方法:使用箭頭函數
btn.addEventListener("click", function() {
  setTimeout(() => {
    console.log(this); // 指向btn元素(繼承外層函數的this)
  }, 100);
});
  1. 類方法中的 this 丟失

類的實例方法作為回調函數時,this 會丟失,需要手動綁定。

class Test {
  constructor(name) {
    this.name = name;
  }
  printName() {
    console.log(this.name);
  }
}
const test = new Test("類方法測試");
const print = test.printName;
print(); // 輸出:undefined(this指向window)
// 解決方法:使用bind綁定this
const boundPrint = test.printName.bind(test);
boundPrint(); // 輸出:類方法測試

this的指向規則看似複雜,實則有跡可循。記住調用方式決定 this 指向這一核心原則,再結合不同場景下的綁定規則和手動改變指向的方法,就能輕鬆駕馭這個 JS 中的 “靈魂” 關鍵詞。