JavaScript中this指向、bind、call、apply、知識(shí)點(diǎn)與相關(guān)面試題匯總
一、JS中 this 究竟指向誰(shuí)?
在 JavaScript 中,this 的指向問(wèn)題常被稱為“面試第一道坎”。其實(shí),掌握它的秘訣只有一句話:
- this 的指向并不取決于函數(shù)定義的位置,而是取決于函數(shù)“如何被調(diào)用”。
- 箭頭函數(shù)的 this 捕獲自定義時(shí)所處的外部(父級(jí))詞法環(huán)境。
1. this 的值在何時(shí)確定?
this 是在執(zhí)行上下文(Execution Context)創(chuàng)建時(shí)確定的。
這意味著:
定義時(shí)無(wú)效: 僅僅聲明一個(gè)普通函數(shù),this 是沒有實(shí)際意義的占位符。
運(yùn)行時(shí)確定: 只有當(dāng)函數(shù)被真正觸發(fā)調(diào)用時(shí),JavaScript 引擎才會(huì)根據(jù)調(diào)用方式把特定的對(duì)象“綁定”給 this。
2. 四大核心綁定規(guī)則
判斷 this 指向時(shí),可以按照以下四個(gè)規(guī)則的優(yōu)先級(jí)進(jìn)行對(duì)比:
① 默認(rèn)綁定 (Default Binding)
當(dāng)函數(shù)作為獨(dú)立調(diào)用(不帶任何修飾的函數(shù)調(diào)用)時(shí),this 指向全局對(duì)象。
非嚴(yán)格模式: 指向 window (瀏覽器) 或 global (Node.js)。
嚴(yán)格模式 (‘use strict’): this 為 undefined。
② 隱式綁定 (Implicit Binding)
當(dāng)函數(shù)作為某個(gè)對(duì)象的方法被調(diào)用時(shí),this 指向該對(duì)象。
const obj = {
name: 'Gemini',
sayHi() { console.log(this.name); }
};
obj.sayHi(); // this 指向 obj,輸出 "Gemini"
- 注意 隱式丟失: 隱式綁定容易出現(xiàn)“丟失”現(xiàn)象。例如將 obj.sayHi 賦值給一個(gè)變量再調(diào)用,就會(huì)退化為默認(rèn)綁定。
③ 顯式綁定 (Explicit Binding)
通過(guò) call()、apply() 或 bind() 直接指定 this 的值。
call/apply:立即執(zhí)行函數(shù)并改變 this。
bind:返回一個(gè)硬綁定了 this 的新函數(shù),之后無(wú)論怎么調(diào)用,this 都不會(huì)再變。
④ new 綁定 (new Binding)
當(dāng)使用 new 關(guān)鍵字調(diào)用構(gòu)造函數(shù)時(shí),JavaScript 內(nèi)部會(huì)創(chuàng)建一個(gè)新對(duì)象,并將 this 指向這個(gè)新創(chuàng)建的實(shí)例對(duì)象。
3. 箭頭函數(shù)的this為何不同?
箭頭函數(shù)是 this 規(guī)則中的“特例”,因?yàn)樗?strong>沒有自己的 this。
- 有何不同: 箭頭函數(shù)的
this捕獲自定義時(shí)所處的外部(父級(jí))詞法環(huán)境。 - 為什么: 在箭頭函數(shù)出現(xiàn)前,回調(diào)函數(shù)(如
setTimeout)中的this經(jīng)常意外指向全局,開發(fā)者不得不使用var self = this這種寫法。箭頭函數(shù)通過(guò)詞法作用域(Lexical Scoping)讓this保持邏輯上的連貫性,即“外層是誰(shuí),我就是誰(shuí)”。 - 不可改變: 由于箭頭函數(shù)本身沒有綁定
this,所以call()、apply()或bind()對(duì)它無(wú)效。
4. 如何判斷復(fù)雜場(chǎng)景下的指向?
面對(duì)嵌套、回調(diào)等復(fù)雜代碼,可以遵循以下 “優(yōu)先級(jí)排除法”(由高到低):
| 優(yōu)先級(jí) | 判定條件 | 結(jié)論 |
|---|---|---|
| 1 (最高) | 函數(shù)是 new 調(diào)用的嗎? | 是: 指向新創(chuàng)建的實(shí)例對(duì)象。 |
| 2 | 函數(shù)通過(guò) call/apply/bind 調(diào)用嗎? | 是: 指向指定的第一個(gè)參數(shù)。 |
| 3 | 函數(shù)在某個(gè)上下文對(duì)象中調(diào)用嗎? | 終點(diǎn): 指向該所屬對(duì)象(如 obj.foo())。 |
| 4 (最低) | 以上都不是(獨(dú)立調(diào)用) | 默認(rèn): 指向全局對(duì)象(嚴(yán)格模式下為 undefined)。 |
兩個(gè)特殊檢查點(diǎn):
先看是否為箭頭函數(shù):
如果是,無(wú)視以上所有規(guī)則,直接看它定義時(shí)外層的普通函數(shù)this是誰(shuí)。回調(diào)函數(shù)的陷阱:
例如setTimeout(obj.foo, 100),這里雖然傳入了obj.foo,但函數(shù)實(shí)際上是在定時(shí)器到期后由全局環(huán)境調(diào)用的,屬于**“隱式丟失”**,this通常指向全局。
總結(jié)公式
誰(shuí)調(diào)用,指誰(shuí);沒調(diào)用,指全局;new 了指實(shí)例;箭頭函數(shù)看“親爹”。
二、js中 this 常見面試題
1. 默認(rèn)綁定與隱式綁定(基礎(chǔ)陷阱)
這是最常見的題型,考察你是否清楚“誰(shuí)調(diào)用指向誰(shuí)”。
var name = "Window";
const obj = {
name: "Object",
getName: function() {
console.log(this.name);
}
};
const bare = obj.getName;
obj.getName(); // 打印什么?
bare(); // 打印什么?
obj.getName():屬于隱式綁定,調(diào)用者是 obj,所以 this 指向 obj。輸出:“Object”。
bare():雖然它拿到了函數(shù)引用,但調(diào)用時(shí)是直接運(yùn)行的(默認(rèn)綁定)。在非嚴(yán)格模式下,this 指向 window。輸出:“Window”。
2. 箭頭函數(shù)(核心差異)
箭頭函數(shù)是面試官最喜歡用來(lái)對(duì)比 this 的工具,因?yàn)?strong>它沒有自己的 this。
const obj = {
name: "ArrowObj",
sayHi: () => {
console.log(this.name);
},
sayHello: function() {
const inner = () => console.log(this.name);
inner();
}
};
obj.sayHi(); // 打印什么?
obj.sayHello(); // 打印什么?
obj.sayHi():箭頭函數(shù)的 this 取決于它定義時(shí)所在的外層作用域。這里外層是全局環(huán)境(不是 obj 的花括號(hào)),所以指向 window。輸出:“Window”。
obj.sayHello():inner 是箭頭函數(shù),它會(huì)找外層非箭頭函數(shù)的 this。它的外層是 sayHello,而 sayHello 是被 obj 調(diào)用的,其 this 是 obj。輸出:“ArrowObj”。
3. 構(gòu)造函數(shù)與顯式綁定(優(yōu)先級(jí))
當(dāng) new、bind、call/apply 同時(shí)出現(xiàn)時(shí),考察的是優(yōu)先級(jí)。
function Foo(name) {
this.name = name;
}
const obj1 = {};
const bar = Foo.bind(obj1);
bar("Jack");
console.log(obj1.name); // 打印什么?
const bazz = new bar("Rose");
console.log(obj1.name); // 打印什么?
console.log(bazz.name); // 打印什么?
bar(“Jack”):使用 bind 將 this 永久綁定到了 obj1。輸出:“Jack”。
new bar(“Rose”):重點(diǎn)!new 綁定的優(yōu)先級(jí)高于 bind。即使函數(shù)被綁定到了 obj1,使用 new 時(shí)依然會(huì)創(chuàng)建一個(gè)新對(duì)象,并將 this 指向這個(gè)新對(duì)象。
obj1.name 依然是 “Jack”,而 bazz.name 是 “Rose”。
4. 定時(shí)器 + 閉包
var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
fn();
arguments[0]();
}
};
obj.method(fn, 1);
fn():作為參數(shù)傳遞后直接調(diào)用,屬于默認(rèn)綁定。輸出:10。
arguments0:這是一個(gè)極具迷惑性的點(diǎn)。arguments[0] 實(shí)際上是調(diào)用 arguments 對(duì)象上的第 0 個(gè)屬性。這屬于隱式綁定,this 指向 arguments 對(duì)象。因?yàn)?arguments 傳入了兩個(gè)參數(shù),它的 length 是 2。輸出:2。
?? 總結(jié):this 指向判斷準(zhǔn)則
你可以按照這個(gè)優(yōu)先級(jí)順序來(lái)快速判斷:
new 綁定:函數(shù)是否在 new 中調(diào)用?如果是,this 指向新創(chuàng)建的對(duì)象。
顯式綁定:函數(shù)是否通過(guò) call、apply 或 bind 調(diào)用?如果是,this 指向指定的對(duì)象。
隱式綁定:函數(shù)是否在某個(gè)上下文對(duì)象中調(diào)用(如 obj.func())?如果是,this 指向那個(gè)對(duì)象。
默認(rèn)綁定:如果以上都不是,在非嚴(yán)格模式下指向 window,嚴(yán)格模式下是 undefined。
箭頭函數(shù):以上規(guī)則統(tǒng)統(tǒng)無(wú)效,請(qǐng)看它外層的函數(shù)/全局作用域的 this 是誰(shuí)。
三、 call、apply、bind 核心知識(shí)點(diǎn)匯總
1. 核心異同對(duì)比表
| 特性 | call | apply | bind |
|---|---|---|---|
| 立即執(zhí)行 | 是 | 是 | 否(返回一個(gè)新函數(shù)) |
| 參數(shù)形式 | 逐個(gè)列舉:fn.call(obj, 1, 2) | 數(shù)組/類數(shù)組:fn.apply(obj, [1, 2]) | 逐個(gè)列舉(支持偏函數(shù)/柯里化傳參) |
| 主要用途 | 借用方法、精準(zhǔn)修改 this | 處理數(shù)組數(shù)據(jù)(如 Math.max) | 鎖定回調(diào)函數(shù)的 this、預(yù)設(shè)參數(shù) |
| 返回值 | 函數(shù)執(zhí)行的結(jié)果 | 函數(shù)執(zhí)行的結(jié)果 | 綁定了 this 后的新函數(shù) |
2. 為什么設(shè)計(jì)它們?(優(yōu)點(diǎn)與場(chǎng)景)
- 代碼復(fù)用:無(wú)需通過(guò)繼承,即可讓一個(gè)對(duì)象直接“借用”另一個(gè)對(duì)象的方法。
- 解耦:將對(duì)象與其方法執(zhí)行的上下文分離,增加邏輯靈活性。
- 解決 this 丟失:在異步回調(diào)(如
setTimeout)或事件處理中,防止this意外指向全局對(duì)象。
3. 手寫模擬實(shí)現(xiàn)(面試高頻)
① 模擬實(shí)現(xiàn)call
核心思路:將函數(shù)設(shè)為目標(biāo)對(duì)象的臨時(shí)屬性,利用隱式綁定規(guī)則執(zhí)行,執(zhí)行完后刪除。
Function.prototype.myCall = function(context, ...args) {
// 【1】確定要綁定的目標(biāo)對(duì)象
// 如果沒傳目標(biāo)對(duì)象,就默認(rèn)是 window
context = context || window;
// 【2】把當(dāng)前函數(shù)“變”成目標(biāo)對(duì)象的一個(gè)方法
// 這里的 this 指向的就是那個(gè)“正在求助”的函數(shù)
const fnKey = Symbol('temporaryFunction');
context[fnKey] = this;
// 【3】通過(guò)對(duì)象來(lái)調(diào)用這個(gè)函數(shù)
// 關(guān)鍵:一旦用 context.xxx() 調(diào)用,函數(shù)內(nèi)部的 this 就會(huì)指向 context
const result = context[fnKey](...args);
// 【4】任務(wù)完成,把剛才臨時(shí)加進(jìn)去的方法刪掉,保持對(duì)象原貌
delete context[fnKey];
// 【5】返回函數(shù)執(zhí)行的結(jié)果
return result;
};
② 模擬實(shí)現(xiàn) apply
邏輯與 call 基本一致,區(qū)別在于對(duì)第二個(gè)參數(shù)(數(shù)組)的處理。
Function.prototype.myApply = function(context, args) {
context = context || window;
const key = Symbol('key');
context[key] = this;
// 判斷 args 是否為數(shù)組或類數(shù)組,并進(jìn)行解構(gòu)傳參
const result = Array.isArray(args) ? context[key](...args) : context[key]();
delete context[key];
return result;
};
③ 模擬實(shí)現(xiàn) bind (進(jìn)階版)
需要考慮:返回新函數(shù)、參數(shù)合并(柯里化)、支持 new 實(shí)例化調(diào)用。
Function.prototype.myBind = function(context, ...args) {
const self = this; // 保存原函數(shù)
const bound = function(...nextArgs) {
// 如果是通過(guò) new 調(diào)用的,this 應(yīng)指向?qū)嵗ù藭r(shí)忽略 bind 傳入的 context)
const isNew = this instanceof bound;
return self.apply(isNew ? this : context, args.concat(nextArgs));
};
// 維護(hù)原型鏈(讓實(shí)例能繼承原函數(shù) prototype 上的屬性)
bound.prototype = Object.create(self.prototype);
return bound;
};
四、call、apply、bind 面試坑點(diǎn)
1. 連續(xù) bind 的結(jié)果
- fn.bind(obj1).bind(obj2)() -> this 依然指向 obj1。
- 原因:bind 返回的是一個(gè)新的包裝函數(shù),第二次 bind 綁定的是這個(gè)包裝函數(shù),而最內(nèi)層的 fn 始終只認(rèn)第一次綁定的 obj1。
核心結(jié)論:包裝盒效應(yīng)
bind 的本質(zhì)是閉包。每調(diào)用一次 bind,就在原函數(shù)外面套了一個(gè)“殼子”。
第一次 bind:把原函數(shù) fn 塞進(jìn)一個(gè)包裝盒,并在盒子里寫死 this 指向 obj1。
第二次 bind:是給這個(gè)包裝盒又套了一個(gè)新盒子,試圖改變包裝盒的 this。
最終執(zhí)行:最外層的盒子被調(diào)用,它去調(diào)用里面的盒子,里面的盒子最終調(diào)用最內(nèi)核的 fn。
關(guān)鍵點(diǎn):內(nèi)核的 fn 已經(jīng)被第一個(gè)盒子用 call 或 apply 鎖死了,外層的盒子再怎么折騰,也改不動(dòng)最深層那個(gè) call 的參數(shù)。
var name = 'Global Window';
const obj1 = { name: 'Object_1' };
const obj2 = { name: 'Object_2' };
const obj3 = { name: 'Object_3' };
function fn() {
console.log(this.name);
}
// ==========================================
// 核心實(shí)驗(yàn):連續(xù)三次綁定
// ==========================================
const bind1 = fn.bind(obj1);
const bind2 = bind1.bind(obj2);
const bind3 = bind2.bind(obj3);
bind3();
/* * 【 打印結(jié)果 】:Object_1
*/
// ==========================================
// 邏輯偽代碼還原:為什么改不動(dòng)?
// ==========================================
// 1. bind1 相當(dāng)于:
const bind1_logic = function(...args) {
return fn.apply(obj1, args); // <--- 這里已經(jīng)把 obj1 寫死了
};
// 2. bind2 相當(dāng)于:
const bind2_logic = function(...args) {
// 這里的 this 指向 obj2,但那又怎樣?
// 它內(nèi)部調(diào)用的是 bind1_logic
return bind1_logic.apply(obj2, args);
};
// 3. 執(zhí)行 bind2() 時(shí):
// 調(diào)用 bind2_logic -> 執(zhí)行 bind1_logic.apply(obj2)
// 內(nèi)部執(zhí)行 bind1_logic() -> 執(zhí)行 fn.apply(obj1)
// 最終 fn 看到的 this 永遠(yuǎn)是 obj1
問(wèn):多次 bind 之后,this 指向誰(shuí)?
答: 始終指向第一次 bind 綁定的對(duì)象。
原因: * bind 方法返回的是一個(gè)新函數(shù)(閉包)。
當(dāng)我們多次 bind 時(shí),實(shí)際上是多層函數(shù)嵌套。
只有最靠近原函數(shù)的那個(gè) bind(即第一次)是通過(guò) apply/call 真正綁定了原函數(shù)的 this。
后續(xù)的 bind 只是在修改“包裝函數(shù)”的 this,而包裝函數(shù)內(nèi)部執(zhí)行原函數(shù)時(shí),依然使用的是第一次綁定好的 obj1。
追問(wèn):那怎么才能改掉 bind 后的 this?
答: 只有一種辦法——使用 new 關(guān)鍵字。因?yàn)?new 綁定的優(yōu)先級(jí)高于 bind 綁定(如果是普通函數(shù)而非箭頭函數(shù)的話)。
2. new 的優(yōu)先級(jí)最高:
通過(guò) bind 綁定的函數(shù),如果被當(dāng)做構(gòu)造函數(shù)使用 new 調(diào)用,原本綁定的 this 會(huì)失效。
總結(jié)
到此這篇關(guān)于JavaScript中this指向、bind、call、apply、知識(shí)點(diǎn)與相關(guān)面試題匯總的文章就介紹到這了,更多相關(guān)JS this指向、bind、call、apply內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JS 實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼的“59秒后重新發(fā)送驗(yàn)證短信”功能
這篇文章主要介紹了JS 實(shí)現(xiàn)發(fā)送短信驗(yàn)證碼的“59秒后重新發(fā)送驗(yàn)證短信”功能,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-08-08
基于JavaScript實(shí)現(xiàn)窗口拖動(dòng)效果
這篇文章主要介紹了基于JavaScript實(shí)現(xiàn)窗口拖動(dòng)效果的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-01-01
Javascript 網(wǎng)頁(yè)水印(非圖片水印)實(shí)現(xiàn)代碼
在一些B/S結(jié)構(gòu)的應(yīng)用系統(tǒng)中,有很多頁(yè)面是需要有水印的。常見的就是公文系統(tǒng)、合同系統(tǒng)等。2010-03-03
小程序異步問(wèn)題之多個(gè)網(wǎng)絡(luò)請(qǐng)求依次執(zhí)行并依次收集請(qǐng)求結(jié)果
這篇文章主要介紹了小程序異步問(wèn)題之多個(gè)網(wǎng)絡(luò)請(qǐng)求依次執(zhí)行并依次收集請(qǐng)求結(jié)果,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-05-05
微信小程序uploadFile接口實(shí)現(xiàn)文件上傳
這篇文章主要介紹了微信小程序uploadFile接口實(shí)現(xiàn)文件上傳流程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
利用JavaScript實(shí)現(xiàn)檢測(cè)用戶是否在線功能
這篇文章主要為大家詳細(xì)介紹了如何利用JavaScript實(shí)現(xiàn)檢測(cè)用戶是否在線功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2022-12-12
JS簡(jiǎn)單實(shí)現(xiàn)點(diǎn)擊跳轉(zhuǎn)登陸郵箱功能的方法
這篇文章主要介紹了JS簡(jiǎn)單實(shí)現(xiàn)點(diǎn)擊跳轉(zhuǎn)登陸郵箱功能的方法,涉及js針對(duì)hash表的遍歷與頁(yè)面元素屬性動(dòng)態(tài)操作相關(guān)實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-10-10

