JavaScript 中的 this 綁定機(jī)制深入解析
深入理解 JavaScript 中的 this 綁定機(jī)制
在 JavaScript 學(xué)習(xí)路徑中,this 關(guān)鍵字絕對是繞不開的核心知識點(diǎn),也是最容易讓人困惑的概念之一。很多開發(fā)者會被其“變幻莫測”的指向搞得暈頭轉(zhuǎn)向,甚至戲稱其為“JavaScript 中的薛定諤”。但實(shí)際上,this 的指向并非毫無規(guī)律,其核心原則只有一個:this 是函數(shù)執(zhí)行時的上下文對象,指向誰完全取決于函數(shù)的調(diào)用方式,而非定義位置。
本文將從核心原則出發(fā),按優(yōu)先級拆解 this 的綁定規(guī)則,結(jié)合實(shí)際場景與案例,幫你徹底理清 this 的指向邏輯。
一、核心認(rèn)知:this 指向的本質(zhì)
在探討具體規(guī)則前,我們需要先建立一個關(guān)鍵認(rèn)知:this 是動態(tài)綁定的,它的指向只在函數(shù)執(zhí)行的那一刻才最終確定。
舉個直觀的例子:
function sayHi() {
console.log(this.name);
}
const person1 = { name: "張三", sayHi };
const person2 = { name: "李四", sayHi };
person1.sayHi(); // 張三
person2.sayHi(); // 李四同一個 sayHi 函數(shù),只因調(diào)用者不同,this 就指向了不同的對象。這正是 this 的核心特性——執(zhí)行上下文決定指向。
而所有 this 綁定場景,都遵循一套明確的優(yōu)先級規(guī)則(從高到低):
new 綁定 > 顯式綁定(call/apply/bind)> 隱式綁定(對象調(diào)用)> 默認(rèn)綁定(全局/嚴(yán)格模式)
二、分場景拆解 this 綁定規(guī)則
1. 默認(rèn)綁定:函數(shù)獨(dú)立調(diào)用時
當(dāng)函數(shù)沒有任何調(diào)用前綴(不是對象屬性、不是 new 調(diào)用、未使用 call/apply/bind),即為獨(dú)立調(diào)用,此時觸發(fā)默認(rèn)綁定。
- 非嚴(yán)格模式:
this指向全局對象(瀏覽器中是window,Node.js 中是global); - 嚴(yán)格模式(
'use strict'):this指向undefined(ES5 引入嚴(yán)格模式的核心目的之一,就是避免意外修改全局變量)。
代碼示例:
// 非嚴(yán)格模式
function foo() {
console.log(this); // window(瀏覽器環(huán)境)
}
foo(); // 獨(dú)立調(diào)用,觸發(fā)默認(rèn)綁定
// 嚴(yán)格模式
function bar() {
'use strict';
console.log(this); // undefined
}
bar();
// 嵌套函數(shù)的默認(rèn)綁定
const obj = {
fn() {
// 這里的 this 是 obj(隱式綁定)
function inner() {
console.log(this); // window(非嚴(yán)格模式,獨(dú)立調(diào)用觸發(fā)默認(rèn)綁定)
}
inner(); // 無調(diào)用前綴,獨(dú)立執(zhí)行
}
};
obj.fn();注意:嵌套函數(shù)的 this 與外層函數(shù)的 this 無關(guān),只要是獨(dú)立調(diào)用,就遵循默認(rèn)綁定。
2. 隱式綁定:函數(shù)作為對象屬性調(diào)用時
當(dāng)函數(shù)作為某個對象的屬性,通過 對象.函數(shù)() 的形式調(diào)用時,this 會指向這個調(diào)用者對象(即 . 前面的對象)。這是日常開發(fā)中最常見的綁定場景之一。
代碼示例:
const user = {
name: "王五",
age: 28,
introduce() {
console.log(`我是${this.name},今年${this.age}歲`);
},
address: {
city: "北京",
showLocation() {
console.log(`我在${this.city}`); // this 指向 address 對象
}
}
};
user.introduce(); // 我是王五,今年28歲(this = user)
user.address.showLocation(); // 我在北京(this = address)常見陷阱:隱式綁定丟失
隱式綁定的核心是“函數(shù)作為對象屬性被調(diào)用”,如果函數(shù)被抽離出來獨(dú)立執(zhí)行,或作為回調(diào)函數(shù)傳遞,就會丟失原對象綁定,退化為默認(rèn)綁定。
典型場景:
const user = {
name: "趙六",
sayHi() {
console.log(this.name);
}
};
// 陷阱1:函數(shù)賦值給獨(dú)立變量
const hi = user.sayHi; // 僅獲取函數(shù)引用,未執(zhí)行
hi(); // undefined(this = window,非嚴(yán)格模式)
// 陷阱2:作為回調(diào)函數(shù)傳遞
setTimeout(user.sayHi, 1000); // undefined(定時器回調(diào)獨(dú)立執(zhí)行)
[1,2,3].forEach(user.sayHi); // undefined(forEach 回調(diào)獨(dú)立執(zhí)行)3. 顯式綁定:用 call/apply/bind 強(qiáng)制指定 this
當(dāng)我們需要手動控制 this 指向時,可以使用函數(shù)原型上的 call、apply、bind 方法,這種方式稱為顯式綁定,優(yōu)先級高于隱式綁定。
三者的核心區(qū)別與用法如下表所示:
| 方法 | 核心作用 | 語法格式 | 執(zhí)行時機(jī) |
|---|---|---|---|
| call | 強(qiáng)制綁定 this,傳遞參數(shù) | fn.call(thisArg, arg1, arg2, ...) | 立即執(zhí)行 |
| apply | 強(qiáng)制綁定 this,數(shù)組形式傳遞參數(shù) | fn.apply(thisArg, [arg1, arg2, ...]) | 立即執(zhí)行 |
| bind | 強(qiáng)制綁定 this,返回新函數(shù)(不立即執(zhí)行) | const newFn = fn.bind(thisArg, arg1, arg2, ...) | 后續(xù)手動執(zhí)行 |
代碼示例:
function printInfo(job) {
console.log(`${this.name},從事${job}工作`);
}
const personA = { name: "錢七" };
const personB = { name: "孫八" };
// call:立即執(zhí)行,參數(shù)逐個傳遞
printInfo.call(personA, "前端開發(fā)"); // 錢七,從事前端開發(fā)工作
// apply:立即執(zhí)行,參數(shù)以數(shù)組傳遞
printInfo.apply(personB, ["后端開發(fā)"]); // 孫八,從事后端開發(fā)工作
// bind:返回新函數(shù),this 永久綁定
const printTeacher = printInfo.bind(personA, "講師");
printTeacher(); // 錢七,從事講師工作
// 即使后續(xù)用 call 嘗試修改,也無效
printTeacher.call(personB, "產(chǎn)品經(jīng)理"); // 錢七,從事講師工作特殊情況:如果給 call/apply/bind 傳遞的 thisArg 是 null 或 undefined,則該參數(shù)會被忽略,this 遵循默認(rèn)綁定:
printInfo.call(null, "設(shè)計(jì)師"); // undefined,從事設(shè)計(jì)師工作(非嚴(yán)格模式)
4. new 綁定:構(gòu)造函數(shù)調(diào)用時
當(dāng)函數(shù)通過 new 關(guān)鍵字調(diào)用時(此時函數(shù)稱為“構(gòu)造函數(shù)”),this 會指向新創(chuàng)建的實(shí)例對象,這是優(yōu)先級最高的綁定規(guī)則。
new 關(guān)鍵字執(zhí)行時,會依次完成 4 件事:
- 創(chuàng)建一個空對象(
const instance = {}); - 讓空對象的
__proto__指向構(gòu)造函數(shù)的prototype(實(shí)現(xiàn)原型繼承); - 構(gòu)造函數(shù)的
this指向這個空對象; - 若構(gòu)造函數(shù)沒有返回對象,則默認(rèn)返回這個新對象。
代碼示例:
function Person(name, age) {
this.name = name;
this.age = age;
console.log(this); // Person { name: '周九', age: 30 }(this 指向新實(shí)例)
}
const person1 = new Person("周九", 30);
console.log(person1.name); // 周九(this 綁定到 person1)
// new 綁定優(yōu)先級高于顯式綁定
const person2 = { name: "吳十" };
const BoundPerson = Person.bind(person2); // 顯式綁定到 person2
const person3 = new BoundPerson("鄭十一", 25);
console.log(person3.name); // 鄭十一(this 指向新實(shí)例 person3,而非 person2)5. 特殊場景:箭頭函數(shù)的 this
ES6 引入的箭頭函數(shù)(() => {})是一個特例——箭頭函數(shù)沒有自己的 this。它的 this 是定義時所在的外層作用域的 this,且一旦綁定就永久固定,無法通過 call/apply/bind/new 修改。
箭頭函數(shù)的設(shè)計(jì)初衷,就是為了解決“嵌套函數(shù) this 丟失”的問題。
代碼示例:
const obj = {
name: "王十二",
asyncTask() {
// 普通函數(shù)作為回調(diào):this 指向 window(默認(rèn)綁定)
setTimeout(function() {
console.log(this.name); // undefined
}, 500);
// 箭頭函數(shù)作為回調(diào):this 繼承外層 asyncTask 的 this(即 obj)
setTimeout(() => {
console.log(this.name); // 王十二
}, 1000);
}
};
obj.asyncTask();
// 箭頭函數(shù)無法用 new 調(diào)用(會報錯)
const ArrowFn = () => {};
new ArrowFn(); // TypeError: ArrowFn is not a constructor
// 箭頭函數(shù)的 this 無法通過 call 修改
const test = () => console.log(this.name);
test.call({ name: "李十三" }); // undefined(this 仍為外層作用域的 this)適用場景:箭頭函數(shù)適合作為回調(diào)函數(shù)(如定時器、數(shù)組方法、Promise 回調(diào)),避免 this 指向混亂;不適合作為構(gòu)造函數(shù)或?qū)ο蠓椒ǎ〞?dǎo)致 this 指向不符合預(yù)期)。
三、this 指向判斷流程圖(優(yōu)先級從高到低)
掌握以下流程圖,可快速判斷任意場景下 this 的指向:
- 函數(shù)是用
new調(diào)用的嗎?→this= 新創(chuàng)建的實(shí)例; - 函數(shù)是用
call/apply/bind調(diào)用的嗎?→this= 手動指定的對象; - 函數(shù)是作為對象的屬性調(diào)用的嗎?→
this= 調(diào)用者對象; - 函數(shù)是獨(dú)立調(diào)用的嗎?→ 非嚴(yán)格模式
this = 全局對象,嚴(yán)格模式this = undefined; - 若是箭頭函數(shù)?→
this= 定義時外層作用域的this。
四、經(jīng)典面試題解析(鞏固理解)
題1:隱式綁定丟失與箭頭函數(shù)對比
const user = {
name: "小明",
fn1() {
setTimeout(function() {
console.log(this.name); // 答案:undefined(默認(rèn)綁定 window)
}, 0);
},
fn2() {
setTimeout(() => {
console.log(this.name); // 答案:小明(繼承 fn2 的 this = user)
}, 0);
}
};
user.fn1();
user.fn2();題2:new 與 bind 優(yōu)先級
function Foo() {
this.value = 100;
}
const obj = { value: 200 };
const BoundFoo = Foo.bind(obj); // 顯式綁定到 obj
const instance = new BoundFoo();
console.log(instance.value); // 答案:100(new 優(yōu)先級高于 bind,this 指向?qū)嵗?
題3:嵌套對象的隱式綁定
const company = {
name: "字節(jié)跳動",
department: {
name: "前端研發(fā)部",
getDepartmentName() {
console.log(this.name);
}
}
};
const getDeptName = company.department.getDepartmentName;
getDeptName(); // 答案:undefined(獨(dú)立調(diào)用,默認(rèn)綁定 window)
company.department.getDepartmentName(); // 答案:前端研發(fā)部(this 指向 department)
五、總結(jié)
JavaScript 中的 this 并非“玄學(xué)”,而是遵循明確的綁定規(guī)則:
- 核心原則:
this指向由函數(shù)調(diào)用方式決定,而非定義位置; - 優(yōu)先級順序:
new 綁定 > 顯式綁定 > 隱式綁定 > 默認(rèn)綁定; - 特殊情況:箭頭函數(shù)無自身
this,繼承外層作用域的this,適合解決回調(diào)函數(shù)this丟失問題; - 常見陷阱:隱式綁定丟失(函數(shù)賦值給獨(dú)立變量、作為回調(diào)傳遞),需通過
bind或箭頭函數(shù)規(guī)避。
掌握以上規(guī)則后,無論遇到多么復(fù)雜的 this 場景,都能按優(yōu)先級逐步拆解,精準(zhǔn)判斷其指向。
到此這篇關(guān)于深入理解 JavaScript 中的 this 綁定機(jī)制的文章就介紹到這了,更多相關(guān)js this綁定機(jī)制內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
微信小程序?qū)崿F(xiàn)計(jì)算器(含歷史記錄)
這篇文章主要為大家詳細(xì)介紹了微信小程序?qū)崿F(xiàn)計(jì)算器,含歷史記錄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-09-09
JavaScript實(shí)現(xiàn)返回頂部按鈕案例
這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)返回頂部按鈕案例,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2021-11-11
JS在IE和FF下attachEvent,addEventListener學(xué)習(xí)筆記
今天小弄了一下JS事件,主要說一下FF和IE兼容的問題2009-11-11
javascript代碼編寫需要注意的7個小細(xì)節(jié)小結(jié)
每種語言都有它特別的地方,對于JavaScript來說,使用var就可以聲明任意類型的變量,這門腳本語言看起來很簡單,然而想要寫出優(yōu)雅的代碼卻是需要不斷積累經(jīng)驗(yàn)的。本文利列舉了JavaScript初學(xué)者應(yīng)該注意的七個細(xì)節(jié),與大家分享。2011-09-09
easyUI實(shí)現(xiàn)(alert)提示框自動關(guān)閉的實(shí)例代碼
下面小編就為大家?guī)硪黄猠asyUI實(shí)現(xiàn)(alert)提示框自動關(guān)閉的實(shí)例代碼。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-11-11

