JavaScript的call和apply
在 JavaScript 中,.call()
和 .apply()
都是 Function 原型上的方法,用于改变函数执行时的上下文对象(即 this
指向),它们的区别仅在于参数传递的形式不同。下面结合几个常见场景,说明它们的实际应用。
1. 基本语法
func.call(thisArg, arg1, arg2, …);
func.apply(thisArg, [arg1, arg2, …]);
- thisArg:调用时函数内部
this
应该指向的对象。 - 参数列表:
call
是逗号分隔的参数序列;apply
是一个数组(或类数组)一次性传入。
2. “借用”其它对象的方法
有时候 A 对象没有某个方法,但 B 对象有,想让 A 使用 B 的方法,就可以借用:
const objA = { x: 10 };
const objB = {x: 5,getX() {return this.x;}
};console.log(objB.getX()); // 5
console.log(objB.getX.call(objA)); // 10
这里用 call(objA)
把 getX
中的 this
强制指向 objA
,就实现了“方法借用”。
3. 传递可变参数(数组转展开)
如果要把一个数组作为参数列表传给函数,用 apply
更方便:
function sum(a, b, c) {return a + b + c;
}const nums = [1, 2, 3];
console.log(sum.apply(null, nums)); // 6
等价于:
console.log(sum.call(null, nums[0], nums[1], nums[2])); // 6
在 ES6 之前,这个技巧常用于 Math.max/Math.min 获取数组最大最小值:
const arr = [5, 1, 8, 3];
const max = Math.max.apply(null, arr); // 8
const min = Math.min.apply(null, arr); // 1
ES6+ 可直接写
Math.max(...arr)
,但在老环境或动态参数场景下,.apply
仍然有效。
4. 构造可重用的“类”或混入(Mixin)
当多个对象需要共享同一组方法时,可以用 call/apply 在初始化时添加属性:
function Person(name) {this.name = name;
}
Person.prototype.say = function () {console.log(`Hi, I'm ${this.name}`);
};function Employee(name, company) {Person.call(this, name); // “继承” Person 构造函数this.company = company;
}
Employee.prototype = Object.create(Person.prototype);
Employee.prototype.constructor = Employee;Employee.prototype.getCompany = function () {console.log(this.company);
};const emp = new Employee('Alice', 'Acme Inc.');
emp.say(); // Hi, I'm Alice
emp.getCompany(); // Acme Inc.
这里 Person.call(this, name)
让 Employee
构造函数复用了 Person
的初始化逻辑。
5. 在事件处理或异步场景中绑定上下文
在回调函数中,this
往往丢失。可用 call
或 apply
手动指定:
const obj = {value: 42,getValueAfterDelay(delay) {setTimeout(function () {// 普通函数中 this 指向全局或 undefinedconsole.log(this.value); }.call(this), delay);}
};obj.getValueAfterDelay(1000);
// 42
不过更常用的是 .bind(this)
,它会返回一个新的函数:
setTimeout(function () {console.log(this.value);
}.bind(obj), 1000);
6. 小结
-
何时用
.call()
需要显式地将若干个独立参数逐一传入,并指定this
。 -
何时用
.apply()
参数已经以数组或类数组形式准备好,想“一次性”传递给函数。 -
与
.bind()
的关系call
/apply
立即执行函数并指定this
。bind
返回一个新函数,后续调用时this
永久绑定。
掌握了这三种方法,就可以灵活地控制函数执行的上下文,以及参数的传递方式,特别是在函数复用、混入、事件回调、动态参数等场景中非常有用。