JavaScript内幕——call比apply快得多

瞟了一眼lodash的源码,无意中看到这样一个函数:

 function apply(func, thisArg, args) {
    switch (args.length) {
        case 0: return func.call(thisArg);
        case 1: return func.call(thisArg, args[0]);
        case 2: return func.call(thisArg, args[0], args[1]);
        case 3: return func.call(thisArg, args[0], args[1], args[2]);
    }
    return func.apply(thisArg, args);
}

瞬间懵逼,不知道作者这样写的原因,往上瞧了瞧,作者给出这样的注解:一个更快的,用来代替Function.prototype.apply的方法

难道call比apply更快?

赶紧看了看ECMScriptg规范,才恍然大悟。

规范中指出了调用apply和call时发生的步骤。

下面,先讲apply方法被调用,再讲call方法被调用的场景。


func.apply(thisArg,argArray);

当apply方法被调用时将执行以下步骤:

步骤一:调用IsCallable

如果IsCallable(func) 返回false,那么抛出一个TypeError的异常。因为这个func不能作为函数被调用。

如果IsCallable(func) 返回true,则进入第二步。

步骤二:判断argArray是不是nullundefined,

如果是,那么就返回调用func内部方法[[Call]]的结果。调用内部方法[[Call]]时会提供thisArg——作为this的值和一个空参数列表。

如果argArray既不是null也不是undefined,那么就进入第三步。

步骤三: 判断argArray的类型是不是object

如果不是Object类型,那么就抛出TypeError异常。
如果是,就进入第四步。

步骤四:定义变量len

len的值是调用argArray内部方法[[Get]] 获取argArray对象length属性值的结果,简单的说,就是len = argArray[length];

步骤五: 定义变量n

n的值是ToUint32(len)的结果

步骤六: 定义变量argList,其值是一个空列表。

步骤七:定义index变量,值为0

步骤八: 重复下面步骤,直到index < n

a. 定义变量indexName,值为 ToString(index)的结果
b.定义变量nextArg,值为用indexName作为参数,调用argArray内部方法[[Get]]的结果。简单讲就是 nextArg = argArray[indexName]。
c.将nextArg插入到argList中,作为列表的最后一个元素。
d.将 index +1

步骤九——最后一步: 返回结果

调用func的内部方法[[Call]]并返回结果。提供两个参数,其中thisArg作为this的值,argList作为函数的参数列表。

根据apply的执行过程,我们也可以知道,apply函数的第二个参数如果是类数组,也能正常工作。

下面看看,call的执行过程。它比apply的调用可简单多了。


func.call (thisArg [ , arg1 [ , arg2, … ] ] )

当call方法在func上被调用时(其中参数有thisArg和可选参数arg1,arg2...),会执行以下步骤:

步骤一:调用IsCallable

如果IsCallable(func) 返回false,那么抛出一个TypeError的异常。因为这个func不能作为函数被调用。

步骤二: 定义变量argList,它是个空列表

步骤三: 如果call调用时,有可选的参数,那么从左往右把arg1,arg2...加入到argList的末尾。

步骤四——最后一步: 返回结果

调用func的内部方法[[Call]]并返回结果。提供两个参数,其中thisArg作为this的值,argList作为函数的参数列表。


得出结论:

比较applycall调用过程的不同,我们可以得出apply比call慢的原因主要是apply方法中,对argArray的参数有多次判断,执行步骤比call多。

你可能感兴趣的:(JavaScript内幕——call比apply快得多)