call()
和 apply()
使用给定的 this
值和参数来调用指定的函数。
Function.prototype.call()
Function.prototype.apply()
它两的功能几乎相同,只是参数不同。
Copy // call() 接受参数列表
fn.call(this, 'apples', 'bananas');
// apply() 接受单个参数数组/类数组对象
fn.apply(this, ['apples', 'bananas']);
fn.apply(this, arguments);
fn.apply(this, {'length':2, '0':'eat', '1':'bananas'});
再配合函数的 arguments
参数和剩余参数 (...args) => {}
,以及展开语法 ...
,这两个函数可以说是一模一样。文中的所有例子,call()
和 apply()
都可以相互替代。
1. 语法
Copy // call
call();
call(thisArg);
call(thisArg, arg1, /* …, */ argN);
// apply
apply(thisArg);
apply(thisArg, argsArray);
参数 thisArg
:在调用函数时使用的 this
值
参数列表 arg1, ..., argN
(可选),an argument list
参数数组或类数组 argsArray
(可选),a single array of arguments
类数组对象:有 length
属性,支持整数下标索引 []
返回值:使用指定的 this
值和参数调用函数的结果
对于一个现有的函数,我们可以给它分配一个不同的 this
对象来进行调用。
有了 call()
和 apply()
,一个对象的函数或方法就能被另外一个不同的对象来调用了,这样就能让我们只编写一次方法,然后在另一个对象中直接使用。
2. 常见用法
2.1 对象的构造函数
使用 call
可以链接到对象的构造函数(类似于 Java)。
Copy // Product (对象)(的构造)函数
function Product(name, price) {
this.name = name;
this.price = price;
}
function Food(name, price) {
Product.call(this, name, price); // 调用 Product()
this.category = "food";
}
function Toy(name, price) {
Product.call(this, name, price); // 调用 Product()
this.category = "toy";
}
const rice = new Food("米饭", 5);
console.log(rice.name, rice.price, rice.category); // 米饭 5 food
console.log(rice instanceof Food); // true
console.log(rice instanceof Product); // false
const robot = new Toy("机器人", 40);
console.log(robot.name, robot.price, robot.category); // 机器人 40 toy
console.log(robot instanceof Toy); // true
console.log(robot instanceof Product); // false
以上代码,完成了 Food 和 Toy 对 Product 的继承。
2.2 调用函数
eg1. 调用函数,并指定上下文
Copy function hi() {
console.log(`${this.animal} 通常睡 ${this.sleepDuration}`);
}
const obj = {
animal: "cats",
sleepDuration: "12 hours"
};
hi.call(obj); // cats 通常睡 12 hours
eg2. 调用函数,且没有传第一个参数
Copy var x = "outer";
function display() {
console.log(this.x);
}
display.call();
// 非严格模式,输出 outer
// 严格模式,TypeError: Cannot read properties of undefined (reading 'x')
eg3. 调用匿名函数
Copy const animals = [
{ species: '熊猫', name: '阿宝' },
{ species: '狮子', name: '辛巴' }
];
function log(i) {
this.print = function () {
console.log(`#${i} ${this.species}: ${this.name}`);
}
this.print();
}
for (let i = 0; i < animals.length; i++) {
log.call(animals[i], i);
}
// #0 熊猫: 阿宝
// #1 狮子: 辛巴
2.3 合并数组
我们可以使用 Array.prototype.push()
把一个或多个元素添加到数组的末尾。但如果给它传一个数组, push()
将会把这个数组作为一个整体(即单个元素),而不是依次添加数组里的元素。
而已有的 Array.prototype.concat()
是创建并返回一个新数组,而不是在现有的数组上追加。
在这种情况下,我们就可以使用 apply()
将数组隐式地“展开”成一系列的参数列表。
Copy const array = ["a", "b"];
const elements = [0, 1, 2];
// apply()
array.push.apply(array, elements);
console.info(array); // ['a', 'b', 0, 1, 2]
// 等价写法
array.push(...elements);
console.info(array); // ['a', 'b', 0, 1, 2, 0, 1, 2]
2.4 内置函数
巧妙地使用 apply()
可以让我们更灵活地使用内置函数。
Copy // 找出数组里的 max 和 min
const numbers = [3, 10, 9, 1, 8];
// 1. 传统做法之循环
let max = -Infinity;
let min = +Infinity;
for (let num of numbers) {
if (num > max) max = num;
if (num < min) min = num;
}
console.log(max, min);
// 2. apply() 和 call()
max = Math.max.apply(null, numbers);
min = Math.min.apply(null, numbers);
console.log(max, min); // 10 1
max = Math.max.call(null, ...numbers);
min = Math.min.call(null, ...numbers);
console.log(max, min); // 10 1
// 3. spread syntax
max = Math.max(...numbers);
min = Math.min(...numbers);
console.log(max, min); // 10 1
需要注意的是,通过这种方式使用 apply()
和 call()
,可能会超出 JavaScript 引擎的参数长度限制,比如 JavaScriptCore 引擎的硬编码参数限制是 65536。所以若会超长,可以考虑先将数组分块。
3. 主要参考