Function.prototype.apply()

Function 实例的 apply() 方法会以给定的 this 值和作为数组(或类数组对象)提供的 arguments 调用该函数。

尝试一下

语法

js
apply(thisArg)
apply(thisArg, argsArray)

参数

thisArg

调用 func 时提供的 this 值。如果函数不处于严格模式,则 nullundefined 会被替换为全局对象,原始值会被转换为对象。

argsArray 可选

一个类数组对象,用于指定调用 func 时的参数,或者如果不需要向函数提供参数,则为 nullundefined

返回值

使用指定的 this 值和参数调用函数的结果。

描述

备注: 这个函数与 call() 几乎完全相同,只是函数参数在 call() 中逐个作为列表传递,而在 apply() 中它们会组合在一个对象中,通常是一个数组——例如,func.call(this, "eat", "bananas")func.apply(this, ["eat", "bananas"])

通常情况下,在调用函数时,函数内部的 this 的值是访问该函数的对象。使用 apply(),你可以在调用现有函数时将任意值分配给 this,而无需先将函数作为属性附加到对象上。这使得你可以将一个对象的方法用作通用的实用函数。

你还可以使用任何类数组对象作为第二个参数。实际上,这意味着它需要具有 length 属性,并且整数(“索引”)属性的范围在 (0..length - 1) 之间。例如,你可以使用一个 NodeList,或者像 { 'length': 2, '0': 'eat', '1': 'bananas' } 这样的自定义对象。你还可以使用 arguments,例如:

js
function wrapper() {
  return anotherFn.apply(null, arguments);
}

使用剩余参数和参数的展开语法,可以重写为:

js
function wrapper(...args) {
  return anotherFn(...args);
}

一般而言,fn.apply(null, args) 等同于使用参数展开语法的 fn(...args),只是在前者的情况下,args 期望是类数组对象,而在后者的情况下,args 期望是可迭代对象

警告: 不要使用 apply() 进行构造函数链式调用(例如,实现继承)。这会将构造函数作为普通函数调用,这意味着 new.targetundefined,从而类会抛出错误,因为它们不能在没有 new 的情况下调用。请改用 Reflect.construct()extends

示例

用 apply() 将数组各项添加到另一个数组

你可以使用 Array.prototype.push() 方法将元素追加到数组中。因为 push() 接受可变数量的参数,所以你也可以一次性添加多个元素。但是,如果你将一个数组传递给 push(),它实际上会将该数组作为单个元素添加,而不是逐个添加元素,导致最终得到一个数组内嵌的数组。另一方面,Array.prototype.concat() 在这种情况下具有期望的行为,但它不会将元素追加到已有数组中,而是创建并返回一个新数组。

在这种情况下,你可以使用 apply 隐式地将一个数组作为一系列参数展开。

js
const array = ["a", "b"];
const elements = [0, 1, 2];
array.push.apply(array, elements);
console.info(array); // ["a", "b", 0, 1, 2]

使用展开语法可以达到相同的效果。

js
const array = ["a", "b"];
const elements = [0, 1, 2];
array.push(...elements);
console.info(array); // ["a", "b", 0, 1, 2]

使用 apply() 和内置函数

巧妙地使用 apply() 可以让你在某些情况下使用内置函数来完成一些任务,而这些任务通常需要手动遍历集合(或使用展开语法)。

例如,我们可以使用 Math.max()Math.min() 来找出数组中的最大值和最小值。

js
// 数组中的最小/最大值
const numbers = [5, 6, 2, 3, 7];

// 用 apply 调用 Math.min/Math.max
let max = Math.max.apply(null, numbers);
// 这等价于 Math.max(numbers[0], …) 或 Math.max(5, 6, …)

let min = Math.min.apply(null, numbers);

// 与基于简单循环的算法相比
max = -Infinity;
min = +Infinity;

for (let i = 0; i < numbers.length; i++) {
  if (numbers[i] > max) {
    max = numbers[i];
  }
  if (numbers[i] < min) {
    min = numbers[i];
  }
}

但要注意:通过使用 apply()(或展开语法)来处理任意长的参数列表,你可能会超过 JavaScript 引擎的参数长度限制。

调用具有太多参数的函数(即超过数万个参数)的后果是未指定的,并且在不同的引擎中会有所不同。(JavaScriptCore 引擎将参数限制硬编码为 65536。)大多数引擎会抛出异常;但并没有规范要求阻止其他行为,例如任意限制应用函数实际接收的参数数量。为了说明后一种情况:假设这样的引擎限制为四个参数(实际限制当然要高得多),那么在上面的示例中,传递给 apply 的参数将变为 5, 6, 2, 3,而不是完整的数组。

如果你的值数组可能会增长到数万个,可以使用混合策略:将数组的片段分批通过 apply 调用函数:

js
function minOfArray(arr) {
  let min = Infinity;
  const QUANTUM = 32768;

  for (let i = 0; i < arr.length; i += QUANTUM) {
    const submin = Math.min.apply(
      null,
      arr.slice(i, Math.min(i + QUANTUM, arr.length)),
    );
    min = Math.min(submin, min);
  }

  return min;
}

const min = minOfArray([5, 6, 2, 3, 7]);

规范

Specification
ECMAScript Language Specification
# sec-function.prototype.apply

浏览器兼容性

BCD tables only load in the browser

参见