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

