Skip to content在
扩展运算符与
概述
call、apply、bind 都定义在 Function.prototype 上,用于在调用函数时显式指定 this。三者的机制不同。
基本概念
call调用函数,第一个参数作为this,后续参数逐个传入。apply调用函数,第一个参数作为this,第二个参数是数组(或类数组),数组元素作为实参传入。bind不调用函数,返回一个新函数,新函数的this被固定,部分参数可预先绑定。
语法
js
func.call(thisArg, arg1, arg2, ...)
func.apply(thisArg, [arg1, arg2, ...])
func.bind(thisArg, arg1, arg2, ...)thisArg:函数运行时绑定的this值。call和bind的后续参数按逗号分隔。apply的第二个参数要求是数组或类数组对象(如arguments),null或undefined会按无参数处理。
示例:基础对比
js
const obj = { multiplier: 2 };
function multiply(x, y) {
return (x + y) * this.multiplier;
}
// call:逐个传参
multiply.call(obj, 3, 4); // 14
// apply:参数打包为数组
multiply.apply(obj, [3, 4]); // 14
// bind:返回新函数,不立即执行
const bound = multiply.bind(obj, 3, 4);
bound(); // 14
// bind 也可以部分绑定参数
const part = multiply.bind(obj, 3);
part(4); // 14call 和 apply 功能相同,区别在于传参方式:call 逐个传,apply 通过数组传。实际使用时按参数形式选择。
bind 与 call/apply 的根本区别是不执行原函数,而是生成一个 this 和部分参数已被锁定的新函数。新函数可以在任意时机调用,剩余的实参会被接在后面。
在 forEach 实现中的应用
以 LeetCode 2804 题“数组原型的 forEach 方法”为例,需要实现 Array.prototype.forEach,迭代时需通过 context 指定回调的 this。
js
/**
* 使用 call
*/
Array.prototype.forEach = function (callback, context) {
const values = this;
for (let i = 0; i < values.length; i++) {
callback.call(context, values[i], i, values);
}
};
/**
* 使用 apply
*/
Array.prototype.forEach = function (callback, context) {
const values = this;
for (let i = 0; i < values.length; i++) {
callback.apply(context, [values[i], i, values]);
}
};
/**
* 使用 bind
*/
Array.prototype.forEach = function (callback, context) {
const values = this;
for (let i = 0; i < values.length; i++) {
callback.bind(context, values[i], i, values)();
}
};三种写法都能正确绑定 this。在同步迭代场景下:
call直接逐个传参,代码简单。apply需要将参数临时封装为数组,多一次数组创建。如果参数本身已经在数组里,apply反而更直接。bind在循环中每次创建新函数再立即调用,开销比call和apply大。除非需要延迟执行,否则没有明显收益。
扩展运算符与 apply
ES6 的扩展运算符可以替代 apply 的许多用法。
js
// 旧写法
func.apply(ctx, argsArray);
// ES6 等价写法
func.call(ctx, ...argsArray);两者存在细微差异:apply 期待数组或类数组,扩展运算符则针对可迭代对象。类似 arguments 这种有 length 但未必可迭代的对象,func.apply(ctx, arguments) 总是可用,而 func.call(ctx, ...arguments) 仅在支持 @@iterator 的环境中才等价。在 Node.js 和现代浏览器中,arguments 也是可迭代的,因此 call 加扩展运算符的写法更为常见。
注意点
- 箭头函数:箭头函数的
this在定义时完成词法绑定,call/apply/bind无法修改其this,传入的thisArg会被忽略。 - 多次
bind:bind生成的函数不能再通过bind改变this。func.bind(ctx1).bind(ctx2)执行时的this仍是ctx1。 - 非严格模式下的
thisArg:若thisArg为null或undefined,函数运行时的this会替换为全局对象(浏览器window,Node.jsglobal);严格模式下则保持原值。 - 性能:频繁使用
bind会不断创建新函数对象,在热点循环或高频回调中需要留意。
应用
- 借用方法:
Array.prototype.slice.call(arguments, 0)可将类数组转为数组。现代环境推荐Array.from(arguments)。 - 循环中指定回调
this:实现forEach、map等迭代方法时,可用call或apply传入thisArg。 - 固定事件处理函数
this:element.addEventListener('click', handler.bind(this)),常用于类组件中将方法绑定到实例。
