瀏覽過網絡上超多篇面經後,關於 this 的考察給我的感覺就是——不會為了難而難。看過很多作者發佈自己對於 this 的“炫技”寫法,再看真實面試和工作場景中的使用。我認為,最重要的還是掌握最基本的使用,“炫技”的一些寫法根本拿不上真實項目,“誰都讀不懂的代碼”不是“高級”而是“一場災難”。
一、綁定規則
- new(構造調用)綁定(優先級最高)
- 用
new F()時,this指向新創建的對象(除非構造函數顯式返回對象)。詳細內容見上篇關於原型的文章。
- 用
- 顯式綁定:
call/apply/bindfn.call(obj, ...)/fn.apply(obj, [...])將this強制設為obj。bind返回已綁定this的新函數。
- 隱式綁定:
obj.fn()- 調用時
this指向調用點左側的對象obj(即.或[]左側)。寫的夠清楚明白了吧,我可太照顧新手小白了~
- 調用時
- **默認綁定(全局/函數調用)**(優先級最低)
- 以普通函數調用
fn()時,非嚴格模式下this指向全局對象(瀏覽器是window),嚴格模式下為undefined。
- 以普通函數調用
- 箭頭函數 — 特殊情況
- 箭頭函數沒有自己的
this,它的this取決於定義時所在的詞法環境(父作用域)的this(即“詞法綁定”)。
- 箭頭函數沒有自己的
記住順序:new 綁定 > 顯示綁定 > 隱式綁定 > 默認綁定,箭頭函數跳出這個順序(詞法綁定)。
二、詳細示例
-
默認調用(函數直接調用)
function foo() { console.log(this); }
foo(); // 嚴格模式下 undefined;非嚴格模式下瀏覽器是 window
在嚴格模式 'use strict' 下 this === undefined,這可以避免意外訪問全局對象。
-
隱式綁定(方法調用)
const obj = {
name: 'obj',
f() { console.log(this.name); }
};
obj.f(); // 'obj',this 指向 obj
如果方法被賦值到變量再調用,會丟失隱式綁定:
const g = obj.f;
g(); // undefined(嚴格)或 window.name(非嚴格) —— 綁定丟失
-
顯式綁定:
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。
-
構造函數 /
new
function Person(name){ this.name = name; }
const p = new Person('Alice');
console.log(p.name); // 'Alice'
new 創建的新對象作為 this,且 this 的原型鏈指向 Person.prototype。
注意:如果構造函數顯式返回對象,則返回該對象;若返回非對象(如字符串),則仍返回新對象。
-
箭頭函數
const obj = {
id: 1,
arrow: () => { console.log(this); },
method() { console.log(this); }
};
obj.arrow(); // arrow 內的 this 來自定義時的外層作用域(比如這裏的 window)
obj.method(); // this 指向 obj
箭頭函數的 this 與普通函數不同:**它沒有自己的 this,始終繼承外層的 this**(適合在回調中保持上下文)。
-
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 元素。
-
call/apply與new的交互
主要觀察 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)或嚴格模式下頂層 this 為 undefined(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 優先)。