龙空技术网

JS探究之call和apply到底哪个更快?

茶无味的一天 972

前言:

眼前咱们对“calljs的用法”都比较讲究,各位老铁们都需要学习一些“calljs的用法”的相关内容。那么小编在网上收集了一些有关“calljs的用法””的相关知识,希望看官们能喜欢,各位老铁们快快来了解一下吧!

我们都知道 call()apply() 是用来改变函数中 this 指向的,它们的共同点是都会立即执行,而如果问到它们之间有什么区别时,我们都会想到一个「传参不同

call 参数要分开传,比如 call(this, 1, 2, 3, ...)

apply 传递参数是数组形式,比如 apply(this, [1,2,3,...])

以上就是一直以来我对两者的全部认知了,直到有天我在 Vue 的源码中看到了这么一段"无意义"的代码:

于是我开始猜想,应该是两者之间还存在着不为人知的性能差异?为了佐证这点,赶紧写了个循环试一下:

let arr = [10,12,123,432,54,67,678,98,342]; // 随便定义一些参数function fn () {}const name = 'call'// const name = 'apply' console.time(name);for (let i = 0; i < 99999999; i++) {  fn[name](this, ...arr) // call  // fn[name](this, arr) // apply}console.timeEnd(name)

console.timeconsole.timeEnd 是很方便的调试技巧。

测试结果如下:

以上测试是带了参数的,下面不传参数,只绑定this测试下:

从结果看好像差异也不是很明显,当时觉得可能是测试数据比较简单吧(其实并不是,后面会说到),不过从平均值来看,还是感觉 call 稍微比 apply 更稳定一些。

以上两组对照都是在谷歌浏览器下进行的,于是我就想在苹果 Safari 浏览器下会是什么结果呢?

结果是非常的Amazing啊,首先一模一样的数据规格,Safari的表现比谷歌差了好多,但是想到我的Safari版本可能比较低(MacOS版本10.15.7),所以执行效率差异这个先按下不表,最主要是这个结果怎么跟谷歌是反过来的,反而 apply 要快很多啊?

一定是我的问题!人一旦清楚认知自己是菜鸟的事实,往往就能很快作出准确的判断。于是我仔细查看刚刚的代码,突然意识到,我在往 call 传参的时候习惯性地使用了 es6展开运算符,在 babeljs () 这个网站上看看 babel 会如何处理上面的代码:

可以看到使用了解构传参的 call 方法经过了 babel 的转译,甚至还多调用了一次 apply,反而变得复杂了,虽然浏览器具体怎么处理我们不得而知,但还是可以看出来解构参数这一步操作的消耗可能蛮大的,于是我改成了正常的传参,像这样:

let arr = [10,12,123,432,54,67,678,98,342];function fn () {}const name = 'call'// const name = 'apply' console.time(name);for (let i = 0; i < 99999999; i++) {  fn[name](this, 10,12,123,432,54,67,678,98,342) // call 这里把参数复制下来传参了  // fn[name](this, arr) // apply}console.timeEnd(name)

再重新跑一遍对照,果然Safari的表现就正常了:

再重新看下谷歌浏览器的对照结果,这下就非常明显了,差距一下拉开了几条街:

到这里我们总算是可以得出结论,call 的性能比 apply 要好。如果上面属于实践出真知,那么下面就该说说原理。在探索过程中我查阅了许多资料,最终还得是 ECMA[1] 上对于两个方法的规范提案解答了我的疑惑,虽然不同的浏览器对于JS规范做出的具体实现是不一样的,但毕竟都遵循着同样的规范,通过它我们就能看清楚本质。

从规范中我们不难看出,apply 在处理参数上很明显比 call 多了两个步骤,但它们却都调用了同一个方法 PrepareForTailCall,而且返回的结果也是同个方法只不过传的第三个参数略有不同而已,所以具体的实现上肯定也是 call 比较纯粹,而 apply 则只是为了方便传递参数而创造的方法,这足以证明 call 性能要优于 apply

以上就是文章的全部内容,希望对你有所帮助!如果觉得文章写得不错,可以点赞收藏,也欢迎关注,我会持续更新更多前端有用的知识与实用技巧,我是茶无味de一天,希望与你共同成长~

引用链接

[1] ECMA:

标签: #calljs的用法