瀏覽過網絡上超多篇面經後,關於 this 的考察給我的感覺就是——不會為了難而難。看過很多作者發佈自己對於 this 的“炫技”寫法,再看真實面試和工作場景中的使用。我認為,最重要的還是掌握最基本的使用,“炫技”的一些寫法根本拿不上真實項目,“誰都讀不懂的代碼”不是“高級”而是“一場災難”。

一、綁定規則

  1. new(構造調用)綁定(優先級最高)
    1. new F() 時,this 指向新創建的對象(除非構造函數顯式返回對象)。

      詳細內容見上篇關於原型的文章。

  2. 顯式綁定​:call / apply / bind
    1. fn.call(obj, ...) / fn.apply(obj, [...])this 強制設為 obj
    2. bind 返回已綁定 this 的新函數。
  3. 隱式綁定​:obj.fn()
    1. 調用時 this 指向調用點左側的對象 obj(即 .[] 左側)。

      寫的夠清楚明白了吧,我可太照顧新手小白了~

  4. ​**默認綁定(全局/函數調用)**​(優先級最低)
    1. 以普通函數調用 fn() 時,非嚴格模式下 this 指向全局對象(瀏覽器是 window),嚴格模式下為 undefined
  5. 箭頭函數 — 特殊情況
    1. 箭頭函數沒有自己的 this,它的 this 取決於定義時所在的詞法環境(父作用域)的 this(即“詞法綁定”)。

記住順序:​new 綁定 > 顯示綁定 > 隱式綁定 > 默認綁定​,箭頭函數跳出這個順序(詞法綁定)。

二、詳細示例

  1. 默認調用(函數直接調用)

function foo() { console.log(this); }
foo(); // 嚴格模式下 undefined;非嚴格模式下瀏覽器是 window

在嚴格模式 'use strict'this === undefined,這可以避免意外訪問全局對象。

  1. 隱式綁定(方法調用)

const obj = {
  name: 'obj',
  f() { console.log(this.name); }
};
obj.f(); // 'obj',this 指向 obj

如果方法被賦值到變量再調用,會丟失隱式綁定:

const g = obj.f;
g(); // undefined(嚴格)或 window.name(非嚴格) —— 綁定丟失
  1. 顯式綁定:call / apply / bind

function show() { console.log(this.name); }
const a = {name:'A'};
show.call(a); // 'A'
show.apply(a); // 'A'

const bound = show.bind(a);
bound(); // 'A'
  • bind 返回新函數並綁定那個 this
  1. 構造函數 / new

function Person(name){ this.name = name; }
const p = new Person('Alice');
console.log(p.name); // 'Alice'

new 創建的新對象作為 this,且 this 的原型鏈指向 Person.prototype

注意​:如果構造函數顯式返回對象,則返回該對象;若返回非對象(如字符串),則仍返回新對象。

  1. 箭頭函數

const obj = {
  id: 1,
  arrow: () => { console.log(this); },
  method() { console.log(this); }
};
obj.arrow(); // arrow 內的 this 來自定義時的外層作用域(比如這裏的 window)
obj.method(); // this 指向 obj

箭頭函數的 this 與普通函數不同:​**它沒有自己的 ​this​​,始終繼承外層的 ​this**​(適合在回調中保持上下文)。

  1. DOM 事件處理器中的 this

el.addEventListener('click', function(e) {
  console.log(this === el); // true(普通函數)
});
el.addEventListener('click', (e) => {
  console.log(this); // 來自外層作用域(不是 el),因此箭頭函數一般不推薦直接作為 handler
});
document.body.onclick = function() {
  console.log(this === document.body); // true
};
document.body.onclick = () => {
  console.log(this); // 箭頭函數繼承外層作用域的 this(通常是 window)
};

DOM 事件的回調(普通函數)中的 this 通常指向觸發事件的 DOM 元素。

  1. call/applynew 的交互

主要觀察 bind 綁定的​ this 和 參數的變化。

function F(name){ this.name = name; }
const BoundF = F.bind({name:'X'});
const obj = new BoundF('Y');
console.log(obj.name); // 'Y' —— new 的 this 優先,bind 的 this 不生效作為 this,但 bind 的預設參數仍然有效

當用 new 來調用 bind 後的函數時,new 創建的實例會成為 this,bind 的 this 被忽略(​但 bind 綁定的參數仍會被應用​)。

三、工作中常見 this 陷阱與解決方式

陷阱 1:方法被當作回調傳遞導致 this 丟失

const obj = {
  val: 1,
  getVal() {
    console.log('this.val', this.val); // undefined
    return this.val;
  },
};
setTimeout(obj.getVal, 0); // this 丟失

解決​:

  • 使用 bind: setTimeout(obj.getVal.bind(obj), 0)
  • 使用箭頭函數包裝:setTimeout(() => obj.getVal(), 0)

    所以我們一般推薦用箭頭函數在異步回調中保留上下文。

陷阱 2:在類/構造函數中忘了綁定方法(React class 組件常見)

新項目不會涉及類組件,只有我們在維護老項目的時候才會遇到。

class C {
  method() { console.log(this); }
}
const c = new C();
const f = c.method;
f(); // this 丟失

解決​:在 constructor 中 this.method = this.method.bind(this),或將方法寫成類字段箭頭函數 method = () => {}(類字段語法)以詞法綁定。

陷阱 3:箭頭函數濫用導致無法獲得期望的 this

  • 在需要動態 this 的場景不要用箭頭函數(比如作為 prototype 上的方法,箭頭會鎖死定義時的 this)。
function Person(name) {
  this.name = name;
}

// 在原型上定義箭頭函數方法,this指向外層作用域(全局作用域)
Person.prototype.sayHi = () => {
  console.log(`Hi, I am ${this.name}`);
};

const p = new Person('Alice');
p.sayHi(); // 輸出:Hi, I am undefined

箭頭函數​沒有自己的 this​,它的 this 來源於​定義時的詞法作用域​。

陷阱 4:忘記嚴格模式導致 this 指向全局

在模塊(ESM)或嚴格模式下頂層 thisundefined(ES 模塊默認嚴格)。

對頂層 this 的操作會造成全局污染。所以這時我們建議開啓嚴格模式。

function showUser() {
  this.name = 'Alice';
}

showUser(); // 沒有使用 new 構造實例或者顯示綁定 this
console.log(window.name); // 輸出 "Alice" —— 意外污染全局

四、this 在不同環境的差別

特別説明:

  • 瀏覽器中 var 聲明的變量會掛載到 window 上,let/const 聲明的變量不會(防止變量污染)。
  • Node.js 默認每一個文件都是一個​**模塊作用域(Module Scope)**​,而非全局。

五、你一定會問這些問題

  • **問:this​ 是綁定在函數還是對象上的?**答:綁定在「調用時」的調用表達式上(通常是調用左側的對象),不是函數本身(除了 bind 的硬綁定和箭頭函數的詞法綁定)。
  • ​**問:為何箭頭函數沒有 ​this?**答:設計目的是讓回調/內部函數能方便訪問外層上下文的 this,避免頻繁 .bind(this)
  • **問:bind​ 後還能被 ​call​ 覆蓋嗎?**答:不能。bind 創建的函數有永久綁定的 this(除非用 new,此時 new 的 this 優先)。