JavaScript 中的 bind、apply、call用法與區(qū)別解析
在 JavaScript 中,bind、apply 和 call 是函數(shù)對象的三個(gè)重要方法,它們的核心作用是改變函數(shù)執(zhí)行時(shí)的上下文(this 指向),但在使用方式和應(yīng)用場景上又存在細(xì)微差異。掌握這三個(gè)方法,不僅能讓我們更靈活地控制函數(shù)執(zhí)行,也是理解 JavaScript 中 this 綁定機(jī)制的關(guān)鍵。
一、bind、apply、call 基本概念與區(qū)別
1.1 共同作用
三者的核心功能一致:在調(diào)用函數(shù)時(shí),指定函數(shù)內(nèi)部 this 的指向。這在處理回調(diào)函數(shù)、事件監(jiān)聽、對象方法借用等場景中尤為重要。
1.2 主要區(qū)別
| 方法 | 調(diào)用方式 | 參數(shù)傳遞方式 | 執(zhí)行時(shí)機(jī) |
|---|---|---|---|
| call | 立即執(zhí)行函數(shù) | 逐個(gè)傳遞參數(shù)(逗號分隔) | 調(diào)用后立即執(zhí)行 |
| apply | 立即執(zhí)行函數(shù) | 以數(shù)組(或類數(shù)組)形式傳遞參數(shù) | 調(diào)用后立即執(zhí)行 |
| bind | 返回一個(gè)新的函數(shù)(綁定了 this 的函數(shù)) | 逐個(gè)傳遞參數(shù)(逗號分隔) | 需手動(dòng)調(diào)用新函數(shù)才執(zhí)行 |
二、具體用法詳解
2.1 call 方法
call 方法的語法:
function.call(thisArg, arg1, arg2, ...);
thisArg:函數(shù)執(zhí)行時(shí)綁定的this值。若為null或undefined,在非嚴(yán)格模式下this會(huì)指向全局對象(瀏覽器中為window,Node.js 中為global)。arg1, arg2, ...:傳遞給函數(shù)的參數(shù)列表(逐個(gè)傳入)。
示例:對象方法借用
const person = {
name: 'Alice',
sayHello: function(greeting) {
console.log(`${greeting}, I'm ${this.name}`);
}
};
const anotherPerson = { name: 'Bob' };
// 調(diào)用 person 的 sayHello 方法,但 this 指向 anotherPerson
person.sayHello.call(anotherPerson, 'Hi'); // 輸出:Hi, I'm Bob2.2 apply 方法
apply 方法的語法:
function.apply(thisArg, [argsArray]);
thisArg:同call,指定this指向。argsArray:參數(shù)數(shù)組(或類數(shù)組對象,如arguments),函數(shù)的參數(shù)將從該數(shù)組中提取。
示例:數(shù)組拼接與合并
當(dāng)需要將一個(gè)數(shù)組的元素添加到另一個(gè)數(shù)組中時(shí),apply 可以便捷地實(shí)現(xiàn):
const arr1 = [1, 2, 3]; const arr2 = [4, 5, 6]; // 利用 apply 傳遞數(shù)組參數(shù),等價(jià)于 arr1.push(4, 5, 6) arr1.push.apply(arr1, arr2); console.log(arr1); // 輸出:[1, 2, 3, 4, 5, 6]
這個(gè)案例中,push 方法原本需要逐個(gè)接收參數(shù),而 apply 讓我們可以直接傳遞數(shù)組 arr2,實(shí)現(xiàn)了數(shù)組的快速合并。
2.3 bind 方法
bind 方法的語法:
const boundFunction = function.bind(thisArg, arg1, arg2, ...);
thisArg:綁定到函數(shù)的this值(永久綁定,無法被再次修改)。arg1, arg2, ...:預(yù)設(shè)的參數(shù)(柯里化參數(shù))。- 返回值:一個(gè)新的函數(shù),該函數(shù)的
this已固定為thisArg,且可預(yù)設(shè)部分參數(shù)。
示例:異步操作中的上下文保存
在異步回調(diào)中,this 指向常常會(huì)丟失,使用 bind 可以提前固定上下文:
const dataProcessor = {
prefix: 'Result:',
process: function(value) {
console.log(`${this.prefix} ${value}`);
}
};
// 模擬異步操作
function fetchData(callback) {
setTimeout(() => {
callback(100); // 回調(diào)函數(shù)執(zhí)行時(shí),this 可能指向全局對象
}, 1000);
}
// 綁定 process 方法的 this 為 dataProcessor
fetchData(dataProcessor.process.bind(dataProcessor));
// 1秒后輸出:Result: 100如果不使用 bind,回調(diào)函數(shù)中的 this 會(huì)指向 window(非嚴(yán)格模式),導(dǎo)致無法正確訪問 dataProcessor 的 prefix 屬性。
三、手寫實(shí)現(xiàn) bind、apply、call
理解原生方法的實(shí)現(xiàn)原理,能幫助我們更深刻地掌握其本質(zhì)。下面分別實(shí)現(xiàn)這三個(gè)方法。
3.1 實(shí)現(xiàn) call 方法
call 方法的核心邏輯:
- 將函數(shù)作為指定
thisArg的臨時(shí)方法(避免污染原對象)。 - 傳入?yún)?shù)并執(zhí)行函數(shù)。
- 刪除臨時(shí)方法,返回函數(shù)執(zhí)行結(jié)果。
Function.prototype.myCall = function(context, ...args) {
// 1. 處理 context,如果為 null/undefined,則指向 window
context = context || window;
// 2. 創(chuàng)建一個(gè)唯一的 key,防止與 context 上的屬性沖突
const uniqueKey = Symbol('fn');
// 3. 將當(dāng)前函數(shù)(this)作為 context 的一個(gè)方法
// this 在這里指向調(diào)用 myCall 的函數(shù)
context[uniqueKey] = this;
// 4. 通過 context 調(diào)用該方法,此時(shí) this 就指向了 context
const result = context[uniqueKey](...args);
// 5. 清理掉這個(gè)臨時(shí)方法,保持對象干凈
delete context[uniqueKey];
// 6. 返回函數(shù)的執(zhí)行結(jié)果
return result;
}測試:
const obj = { name: 'Test' };
function testCall(greeting) {
console.log(`${greeting}, ${this.name}`);
}
testCall.myCall(obj, 'Hello'); // 輸出:Hello, Test3.2 實(shí)現(xiàn) apply 方法
apply 與 call 的區(qū)別僅在于參數(shù)傳遞方式(數(shù)組 vs 逐個(gè)),實(shí)現(xiàn)邏輯類似:
Function.prototype.myApply = function(thisArg, argsArray) {
thisArg = thisArg || window;
const fnSymbol = Symbol('fn');
thisArg[fnSymbol] = this;
// 處理參數(shù):若 argsArray 不存在,傳入空數(shù)組
const result = thisArg[fnSymbol](...(argsArray || []));
delete thisArg[fnSymbol];
return result;
};測試:
function sum(a, b, c) {
return a + b + c;
}
const numbers = [1, 2, 3];
console.log(sum.myApply(null, numbers)); // 輸出:63.3 實(shí)現(xiàn) bind 方法
bind 的實(shí)現(xiàn)更復(fù)雜,需要滿足:
- 綁定
this指向(永久綁定,即使使用call/apply也無法修改)。 - 支持預(yù)設(shè)參數(shù)(柯里化參數(shù))。
- 返回的函數(shù)可以被當(dāng)作構(gòu)造函數(shù)使用(此時(shí)
this應(yīng)指向?qū)嵗龑ο螅?/li>
Function.prototype.myBind = function (context, ...bindArgs) {
// this 指向調(diào)用 myBind 的原始函數(shù)
const originalFunc = this;
// 返回一個(gè)新的函數(shù)
const fBound = function (...callArgs) {
// 合并 bind 時(shí)傳入的參數(shù)和調(diào)用時(shí)傳入的參數(shù)
const allArgs = [...bindArgs, ...callArgs];
// 關(guān)鍵點(diǎn):判斷是否是通過 new 關(guān)鍵字調(diào)用的
if (this instanceof fBound) {
// 箭頭函數(shù)不能作為構(gòu)造函數(shù),原生 bind 返回 undefined
if (!originalFunc.prototype) {
return undefined;
}
// 是 new 關(guān)鍵字調(diào)用的,this 應(yīng)該指向新實(shí)例
return new originalFunc(...allArgs);
}
// 如果是普通調(diào)用,就使用 call/apply 將 this 指向綁定的 context
return originalFunc.apply(context, allArgs);
};
// 處理原型鏈:讓返回的函數(shù)的原型指向原始函數(shù)的原型
// 這樣,由 bind 后的函數(shù) new 出來的實(shí)例,也能繼承原始函數(shù)的原型方法
if (originalFunc.prototype) {
fBound.prototype = Object.create(originalFunc.prototype);
fBound.prototype.constructor = fBound;// 確保 constructor 指向正確
}
return fBound;
}測試 1:綁定 this 與參數(shù)柯里化
const obj = { x: 10 };
function add(y, z) {
return this.x + y + z;
}
const boundAdd = add.myBind(obj, 5);
console.log(boundAdd(3)); // 輸出:18(10 + 5 + 3)測試 2:作為構(gòu)造函數(shù)使用
function Person(name) {
this.name = name;
}
const BoundPerson = Person.myBind({});
const person = new BoundPerson('Alice');
console.log(person.name); // 輸出:Alice(this 指向?qū)嵗?,而非綁定?{})
console.log(person instanceof Person); // 輸出:true(原型鏈正確)四、實(shí)際應(yīng)用場景
4.1 借用對象方法
當(dāng)需要使用某個(gè)對象的方法處理另一個(gè)對象的數(shù)據(jù)時(shí),call 和 apply 非常實(shí)用。例如,用數(shù)組的 slice 方法將類數(shù)組轉(zhuǎn)為真正的數(shù)組:
function convertToArray() {
// arguments 是類數(shù)組,借用數(shù)組的 slice 方法
return Array.prototype.slice.call(arguments);
}
console.log(convertToArray(1, 2, 3)); // 輸出:[1, 2, 3]4.2 回調(diào)函數(shù)中的 this 綁定
在定時(shí)器、事件監(jiān)聽等場景中,回調(diào)函數(shù)的 this 往往會(huì)丟失原上下文,bind 可以固定 this 指向:
class Timer {
constructor() {
this.seconds = 0;
// 綁定 tick 方法的 this 為當(dāng)前實(shí)例
setInterval(this.tick.bind(this), 1000);
}
tick() {
this.seconds++;
console.log(this.seconds);
}
}
new Timer(); // 每秒輸出:1, 2, 3, ...? 現(xiàn)代替代方案:箭頭函數(shù)
在 ES6+ 的環(huán)境中,處理上述場景有了更簡潔的選擇:箭頭函數(shù) (=>)。
箭頭函數(shù)沒有自己的 this,它會(huì)捕獲其所在上下文的 this 值作為自己的 this(詞法作用域 this)。因此,它可以完美地替代 bind 來固定上下文。
上面的 Timer 類可以寫成:
class Timer {
constructor() {
this.seconds = 0;
// 使用箭頭函數(shù),tick 方法中的 this 會(huì)自動(dòng)指向 Timer 實(shí)例
setInterval(() => this.tick(), 1000);
}
tick() {
this.seconds++;
console.log(this.seconds);
}
}
// 或者更推薦的方式,將方法本身定義為箭頭函數(shù)(作為類字段)
class ModernTimer {
constructor() {
this.seconds = 0;
setInterval(this.tick, 1000); // 無需 bind 或箭頭函數(shù)包裹
}
tick = () => {
this.seconds++;
console.log(this.seconds);
}
}
new ModernTimer();盡管箭頭函數(shù)在很多情況下更方便,但理解 bind 的工作原理依然是 JavaScript 基礎(chǔ)中不可或缺的一環(huán)。
4.3 函數(shù)柯里化
bind 可以實(shí)現(xiàn)函數(shù)柯里化(將多參數(shù)函數(shù)轉(zhuǎn)為單參數(shù)函數(shù)的鏈?zhǔn)秸{(diào)用),提高代碼復(fù)用性:
function multiply(a, b, c) {
return a * b * c;
}
// 預(yù)設(shè)第一個(gè)參數(shù)為 2
const multiplyBy2 = multiply.bind(null, 2);
// 再預(yù)設(shè)第二個(gè)參數(shù)為 3
const multiplyBy2And3 = multiplyBy2.bind(null, 3);
console.log(multiplyBy2And3(4)); // 輸出:24(2 * 3 * 4)五、注意事項(xiàng)
- 嚴(yán)格模式與非嚴(yán)格模式的區(qū)別:在嚴(yán)格模式下,若
thisArg為null或undefined,函數(shù)內(nèi)的this會(huì)保持null或undefined;非嚴(yán)格模式下則指向全局對象。 - bind 的不可修改性:用
bind綁定的this無法被call或apply再次修改,但可以通過new調(diào)用改變(此時(shí)this指向?qū)嵗?/li> - 性能考量:頻繁使用
bind會(huì)創(chuàng)建新的函數(shù)對象,可能影響性能(尤其在循環(huán)中),需合理使用。
六、總結(jié)
bind、apply 和 call 是 JavaScript 中控制 this 指向的核心工具,它們的應(yīng)用貫穿于日常開發(fā)的方方面面:
- call:適合參數(shù)數(shù)量固定的場景,立即執(zhí)行函數(shù)。
- apply:適合參數(shù)以數(shù)組形式存在的場景(如動(dòng)態(tài)參數(shù)列表),立即執(zhí)行函數(shù)。
- bind:適合需要延遲執(zhí)行函數(shù)、固定
this指向或?qū)崿F(xiàn)參數(shù)柯里化的場景。
通過手動(dòng)實(shí)現(xiàn)這三個(gè)方法,我們不僅能更深入地理解其工作原理,也能對 JavaScript 中的 this 綁定、函數(shù)調(diào)用機(jī)制有更清晰的認(rèn)知。在實(shí)際開發(fā)中,根據(jù)具體場景選擇合適的方法,能讓代碼更簡潔、高效。
到此這篇關(guān)于JavaScript 中的 bind、apply、call用法與區(qū)別解析的文章就介紹到這了,更多相關(guān)js bind apply call用法內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript常用數(shù)組元素搜索或過濾的四種方法詳解
這篇文章主要介紹了JavaScript常用數(shù)組元素搜索或過濾的四種方法,每種方式通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-08-08
基于js實(shí)現(xiàn)的限制文本框只可以輸入數(shù)字
本文主要介紹了js限制文本框只可以輸入數(shù)字的實(shí)例代碼,可復(fù)制直接調(diào)用函數(shù)實(shí)現(xiàn)其功能。需要的朋友可以看下2016-12-12
js+CSS實(shí)現(xiàn)彈出居中背景半透明div層的方法
這篇文章主要介紹了js+CSS實(shí)現(xiàn)彈出居中背景半透明div層的方法,涉及javascript操作彈出div層的操作技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-02-02
js實(shí)現(xiàn)自定義滾動(dòng)條的示例
這篇文章主要介紹了js實(shí)現(xiàn)自定義滾動(dòng)條的示例,幫助大家制作JS特效,美化自身網(wǎng)頁,感興趣的朋友可以了解下2020-10-10
JavaScript設(shè)計(jì)模式之迭代者模式詳情
這篇文章主要介紹了JavaScript設(shè)計(jì)模式之迭代者模式詳情,迭代器設(shè)計(jì)模式能夠可以讓我們更方便的且有規(guī)矩的進(jìn)行訪問復(fù)合數(shù)據(jù)的每一項(xiàng),也可以通過迭代器進(jìn)行完成一些流線式操作2022-06-06
JavaScript中Array.from()的超全用法詳解
Array.from方法用于將兩類對象轉(zhuǎn)為真正的數(shù)組:類似數(shù)組的對象(array-like?object)和可遍歷(iterable)的對象(包括?ES6?新增的數(shù)據(jù)結(jié)構(gòu)?Set?和?Map),別忘記就來講講他的具體用法吧2023-03-03
詳解ES6數(shù)組方法find()、findIndex()的總結(jié)
這篇文章主要介紹了詳解ES6數(shù)組方法find()、findIndex()的總結(jié),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05

