JavaScript 函数 apply、call 与 bind 方法详解及模拟实现
本文深入解析 JavaScript 中用于改变函数执行上下文(this)的三个核心方法:call、apply 和 bind。通过对比它们的参数传递方式、返回值特性以及在严格模式与非严格模式下的表现差异,帮助开发者精准掌握其用法。文章进一步提供了在 Function 原型对象上模拟实现这三个方法的详细步骤与代码示例,揭示其底层原理。
核心机制与相同点
call、apply 和 bind 均作用于函数对象,核心目的是改变目标函数执行时内部 this 的指向。它们具有以下几个共同特征:
- 修改 this 指向:所有方法都允许显式指定函数执行时的上下文对象。
- 首参决定 this:方法的第一个参数用于指定目标函数内部的
this值。 - 支持多参数传递:均可向目标函数传递额外参数。
- null/undefined 处理规则:若不传参或传入
undefined、null:- 在非严格模式下,目标函数内部的
this将指向全局对象(浏览器中为window)。 - 在严格模式('use strict')下,
undefined保持为undefined,null保持为null。
- 在非严格模式下,目标函数内部的
第1步:验证非严格模式下的 this 指向
当未提供上下文或提供空值时,非严格模式会将 this 隐式转换为全局对象。
/* 正常模式 */
let obj = {
sum(a, b) {
console.log(this)
return a + b
}
}
// 不传参或传入 null/undefined,this 均指向 window
obj.sum.call() // 打印 window
obj.sum.call(null) // 打印 window
第2步:验证严格模式下的 this 指向
启用严格模式后,原生行为被保留,不再强制转换到全局对象。
/* 严格模式 */
'use strict'
obj.sum.call() // 打印 undefined
obj.sum.call(null) // 打印 null
三大方法的核心区别
虽然都能改变 this,但在参数处理和执行时机上有显著差异:
- 参数传递格式:
call():接收多个独立参数,语法为func.call(thisArg, arg1, arg2, ...)。apply():接收两个参数,第二个参数必须是数组(或类数组对象),语法为func.apply(thisArg, [arg1, arg2])。bind():与 call 类似,返回新函数。
- 执行时机:
call和apply:立即执行目标函数。bind:不立即执行,而是返回一个绑定了 this 的新函数,需手动调用。
- 特殊能力:
bind实现了函数柯里化,允许分次绑定参数。
call() 方法详解
call() 方法立即执行目标函数。除第一个参数指定 this 外,后续每个参数依次传递给目标函数。
第1步:使用 call 改变 this 并传参
适用于参数数量已知且较少的场景。
let fn = function(a, b) {
console.log(this.val, a, b);
};
fn.call({val: 10}, 1, 2); // 输出: 10, 1, 2
apply() 方法详解
apply() 同样立即执行目标函数。其独特之处在于第二个参数接受一个数组,常配合 Math.max 等需要展开参数的内置函数使用。
第1步:利用 apply 处理数组参数
这是应用最广泛的场景之一,例如找出数组中的最大值。
let numbers = [5, 6, 2, 3, 7];
let max = Math.max.apply(null, numbers); // 等价于 Math.max(...numbers)
console.log(max); // 输出: 7
bind() 方法详解
bind() 方法不会执行函数,而是创建一个新的绑定函数。它支持参数预设(柯里化)。
第1步:bind 实现参数预设与延迟执行
let fn = function(a, b, c) {
console.log(this.val, a, b, c);
};
let boundFn = fn.bind({val: 10}, 1, 2);
boundFn(3); // 输出: 10, 1, 2, 3
模拟实现核心逻辑
理解原型链与对象属性挂载是模拟实现的关键。以下实现统一了非严格与严格模式下 null/undefined 的处理,将其默认指向 window(即全局对象)。
第1步:模拟 myCall
将目标函数作为 context 对象的临时属性执行,从而劫持 this。
Function.prototype.myCall = function (context, ...args) {
if (context === undefined || context === null) {
context = window;
}
// 核心:将当前函数(this)挂载到 context 上
context.fn = this;
// 执行并获取结果
const result = context.fn(...args);
// 清理临时属性
delete context.fn;
return result;
};
// 测试
let obj1 = { val: 1 };
let obj2 = { val: 9 };
let add = function(a, b) { return this.val + a + b; };
add.myCall(obj2, 2, 3); // 输出: 14
第2步:模拟 myApply
逻辑与 call 基本一致,仅参数处理方式不同,接收数组而非剩余参数列表。
Function.prototype.myApply = function (context, args) {
if (context === undefined || context === null) {
context = window;
}
context.fn = this;
let result;
// 判断是否传入了参数数组
if (args) {
result = context.fn(...args);
} else {
result = context.fn();
}
delete context.fn;
return result;
};
add.myApply(obj2, [2, 3]); // 输出: 14
第3步:模拟 myBind
bind 的实现最为复杂,需要保存原始函数引用,并在返回的新函数中再次进行绑定和参数拼接。
Function.prototype.myBind = function (context, ...initArgs) {
if (context === undefined || context === null) {
context = window;
}
const _this = this; // 缓存原始函数
return function (...args) {
context.fn = _this;
// 合并 bind 预设的参数和调用时的参数
const result = context.fn(...initArgs, ...args);
delete context.fn;
return result;
};
};
// 测试柯里化效果
let curried = add.myBind(obj2, 2);
curried(3); // 输出: 14
总结
call 和 apply 的核心在于“借调执行”,主要区别在于参数传入的形式;bind 的核心在于“创建副本”,用于延时执行或参数预设。在面试或实际开发中,手写模拟实现有助于深入理解 JavaScript 的原型链机制、对象属性操作以及 this 的动态绑定原理。以上就是 JavaScript 函数 apply、call 与 bind 方法及模拟实现的详细内容,更多关于 JavaScript 高级编程技巧的资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













