Skip to main content

apply()

语法

apply(this: Function, thisArg: any, argArray?: any): any;

描述

该方法是 call 的语法糖,它的第一个参数和 call 方法相同,但第二个参数接收一个数组或者类数组,而 call 从第二个参数起接收一个参数序列。

示例

关于 apply,最经典的示例应该就是 Math.max() 传参的问题了。

Math.max(1, 2, 3); // 3
Math.max.apply(this, [1, 2, 3]); // 3
Math.max(...[1, 2, 3]); // 3

此外,它还可以做数组之间的追加。

const arr1 = ['a', 'b', 'c'];
const arr2 = [1, 2, 3];

Array.prototype.push.apply(arr1, arr2);
console.log(arr1); // ["a", "b", "c", 1, 2, 3]

// 当然更现代化的方式是使用展开运算符
[...arr1, ...arr2];

// 数组有一个 concat 的方法,也可以做数组的合并
arr1.concat(arr2);

手写 apply 方法

Function.prototype.apply2 = function(thisArg) {
const fn = Symbol('fn');

if (!thisArg || thisArg === null || thisArg === undefined) {
thisArg = window;
}

const packing = Object(thisArg);
packing[fn] = this;

if (arguments[1]) {
const result = packing[fn](...arguments[1]); // 关键步骤
delete packing[fn];
return result;
} else {
const result = packing[fn]();
delete packing[fn];
return result;
}
};

为什么 call 的性能比 apply 性能高?

apply 运行原理

1、当函数不可调用时,直接抛出一个 TypeError 的异常。

2、如果 argArray 为 null 或者 undefined 或者未声明,则返回调用 Function 的 [[Call]] 内部方法的结果,提供 thisArg 和一个空数组作为参数。

3、如果 argArray 不是对象,则抛出 TypeError 异常。

4、获取 argArray 的长度。调用 argArray 的 [[Get]] 内部方法,找到属性 length。 赋值给 len。

5、定义 n 为 ToUint32。

6、初始化 argList 为一个空列表。

7、初始化 index 为 0。

8、循环迭代取出 argArray。重复循环 while(index < n)

  • a、将下标转换成 String 类型。初始化 indexName 为 ToString(index)。
  • b、定义 nextArg 为 使用 indexName 作为参数调用 argArray 的[[Get]]内部方法的结果。
  • c、将 nextArg 添加到 argList 中,作为最后一个元素。
  • d、设置 index = index + 1

9、返回调用 Function 的 [[Call]] 内部方法的结果,提供 thisArg 作为该值,argList 作为参数列表。

call 运行原理

1、当函数不可调用时,直接抛出一个 TypeError 的异常。

2、定义 argList 为一个空列表。

3、如果使用超过一个参数调用此方法,则以从 arg1 开始的从左到右的顺序将每个参数附加为 argList 的最后一个元素

4、返回调用 func 的[[Call]]内部方法的结果,提供 thisArg 作为该值,argList 作为参数列表。

综上所述,call 接收的参数序列正是引擎内部所需要的格式,而 apply 仅仅是 call 的语法糖,在编译过程需要做一定的格式转换。

参考

深入浅出妙用 Javascript 中 apply、call、bind

为什么 call 比 apply 快?