瞟了一眼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是不是null
或undefined
,
如果是,那么就返回调用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作为函数的参数列表。
得出结论:
比较apply
与call
调用过程的不同,我们可以得出apply比call慢的原因主要是apply
方法中,对argArray
的参数有多次判断,执行步骤比call多。