1.4.6 call, apply

用提供的对象来调用特定函数

call()apply() 使用给定的 this 值和参数来调用指定的函数。

  • Function.prototype.call()

  • Function.prototype.apply()

它两的功能几乎相同,只是参数不同。

// 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. 语法

// call
call();
call(thisArg);
call(thisArg, arg1, /* …, */ argN);

// apply
apply(thisArg);
apply(thisArg, argsArray);
  1. 参数 thisArg:在调用函数时使用的 this

  2. 参数列表 arg1, ..., argN(可选),an argument list

  3. 参数数组或类数组 argsArray(可选),a single array of arguments

    • 类数组对象:有 length 属性,支持整数下标索引 []

  4. 返回值:使用指定的 this 值和参数调用函数的结果

对于一个现有的函数,我们可以给它分配一个不同的 this 对象来进行调用。

有了 call()apply(),一个对象的函数或方法就能被另外一个不同的对象来调用了,这样就能让我们只编写一次方法,然后在另一个对象中直接使用。

2. 常见用法

2.1 对象的构造函数

使用 call 可以链接到对象的构造函数(类似于 Java)。

// 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. 调用函数,并指定上下文

function hi() {
    console.log(`${this.animal} 通常睡 ${this.sleepDuration}`);
}

const obj = {
    animal: "cats",
    sleepDuration: "12 hours"
};

hi.call(obj); // cats 通常睡 12 hours

eg2. 调用函数,且没有传第一个参数

var x = "outer";

function display() {
    console.log(this.x);
}

display.call();
// 非严格模式,输出 outer
// 严格模式,TypeError: Cannot read properties of undefined (reading 'x')

eg3. 调用匿名函数

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() 将数组隐式地“展开”成一系列的参数列表。

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() 可以让我们更灵活地使用内置函数。

// 找出数组里的 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. 主要参考

Last updated