JS探究之call和apply到底哪个更快?
我们都知道 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.time
和console.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 上对于两个方法的规范提案解答了我的疑惑,虽然不同的浏览器对于JS规范做出的具体实现是不一样的,但毕竟都遵循着同样的规范,通过它我们就能看清楚本质。
从规范中我们不难看出,apply
在处理参数上很明显比 call
多了两个步骤,但它们却都调用了同一个方法 PrepareForTailCall
,而且返回的结果也是同个方法只不过传的第三个参数略有不同而已,所以具体的实现上肯定也是 call
比较纯粹,而 apply
则只是为了方便传递参数而创造的方法,这足以证明 call
性能要优于 apply
。
以上就是文章的全部内容,希望对你有所帮助!如果觉得文章写的不错,可以点赞收藏,也欢迎关注,我会持续更新更多前端有用的知识与实用技巧,我是茶无味de一天,希望与你共同成长~
相关文章
- prototype.js的系列文章——关于prototype.js
- 【说站】js中Promise的状态探究
- Js排序算法_js 排序算法
- html 转 js_js加载html字符串
- JS实现函数重载
- JS对象转数组_js怎么把数组转成对象
- JS获取当前年份_js获取当前时间年月日
- vue如何引用外部js_引入外部js文件
- 疯了!js中this到底指向什么?
- 网页防止复制js代码
- js引擎的执行机制详解
- 原生 JS 实现 HTML 转 Markdown,以及其实现逻辑(html2md.js 或 html2markdown.js)
- 如何使用jscythe并通过Node.js的Inspector机制执行任意JS代码
- clipboard.js:最轻便的复制页面内容到剪切板的JS
- 使用Node.js连接MySQL数据库(node-mysql)
- js为一个对象Object添加一个新的属性和值详解编程语言
- JS惰性载入函数与分支函数
- JS借助image元素发送远程请求(案例实战)
- 使用JS连接MySQL数据库:实现化繁为简(js连接mysql数据库)
- 使用 JS 操作 Redis:实现强大数据存储.(js调用redis)
- JS实时链接Oracle让数据库访问更便捷(js实时连接oracle)
- JS在Oracle中的应用(js如何oracle)
- JavaScript探索之旅掌握Oracle和JS的完美融合(js与oracle)
- js计算页面刷新的次数
- js日期字符串截取分割成单个具体的日期(2009-12-3013:28:29)
- JS高级笔记
- JS实现商品倒计时实现代码
- JS仿百度搜索自动提示框匹配查询功能
- js获取当前地址JS获取当前URL的示例代码