一行代码引发的 JS 探究 : call 和 apply 到底哪个更快?

简介: 如果问到它们之间有什么区别时,你可能会说:传参不同。但你知道它们哪个性能更好吗?

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

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

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

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

image.png

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

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 是很方便的调试技巧。

测试结果如下:

call 多次运行结果: apply 多次运行结果:
image.png image.png

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

call 多次运行结果: apply 多次运行结果:
image.png image.png

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

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

call in Safari apply in Safari
image.png image.png

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

image.png

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

image.png

可以看到使用了解构传参的 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 in Safari apply in Safari
image.png image.png

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

call 多次运行结果: apply 多次运行结果:
image.png image.png

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

Call Apply
image.png image.png

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

说到底 callapply 到底哪个更快,究竟选择哪个对我们日常工作的产出或许并无多少影响,甚至很多人可能从来都没用到过,但我从不觉得探索和求知是无意义的,倒不如说这才是编程开发永远的主旋律,如果你看到这里觉得有所收获,那就再好不过了~

相关文章
|
2月前
|
JavaScript
短小精悍的js代码
【10月更文挑战第17天】
126 58
|
24天前
|
JavaScript
原生js炫酷随机抽奖中奖效果代码
原生js随机抽奖是一个炫酷的根据数据随机抽奖的代码,该网页可进行随机抽取一个数据,页面动画高科技、炫酷感觉的随机抽奖效果,简单好用,欢迎下载!
41 3
原生js炫酷随机抽奖中奖效果代码
|
11天前
|
JavaScript 前端开发
js中的bind,call,apply方法的区别以及用法
JavaScript中,`bind`、`call`和`apply`均可改变函数的`this`指向并传递参数。其中,`bind`返回一个新函数,不立即执行;`call`和`apply`则立即执行,且`apply`的参数以数组形式传递。三者在改变`this`指向及传参上功能相似,但在执行时机和参数传递方式上有所区别。
|
4天前
|
JSON JavaScript 关系型数据库
node.js连接GBase 8a 数据库 并进行查询代码示例
node.js连接GBase 8a 数据库 并进行查询代码示例
|
29天前
|
JavaScript 前端开发 开发者
如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码,包括安装插件、配置 ESLint 和 Prettier 以及 VSCode 设置的具体步骤
随着前端开发技术的快速发展,代码规范和格式化工具变得尤为重要。本文介绍了如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码,包括安装插件、配置 ESLint 和 Prettier 以及 VSCode 设置的具体步骤。通过这些工具,可以显著提升编码效率和代码质量。
285 4
|
1月前
|
JSON 移动开发 数据格式
html5+css3+js移动端带歌词音乐播放器代码
音乐播放器特效是一款html5+css3+js制作的手机移动端音乐播放器代码,带歌词显示。包括支持单曲循环,歌词显示,歌曲搜索,音量控制,列表循环等功能。利用json获取音乐歌单和歌词,基于html5 audio属性手机音乐播放器代码。
90 6
|
27天前
|
JavaScript 前端开发 开发者
如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码
随着前端开发技术的快速发展,代码规范和格式化工具变得尤为重要。本文介绍如何在 Visual Studio Code (VSCode) 中使用 ESLint 和 Prettier 检查代码规范并自动格式化 Vue.js 代码。通过安装和配置这些工具,可以确保代码风格一致,提高代码质量和可读性。
58 1
|
2月前
|
JavaScript 前端开发
JS高级—call(),apply(),bind()
【10月更文挑战第17天】call()`、`apply()`和`bind()`是 JavaScript 中非常重要的工具,它们为我们提供了灵活控制函数执行和`this`指向的能力。通过合理运用这些方法,可以实现更复杂的编程逻辑和功能,提升代码的质量和可维护性。你在实际开发中可以根据具体需求,选择合适的方法来满足业务需求,并不断探索它们的更多应用场景。
11 1
|
2月前
|
自然语言处理 JavaScript 前端开发
深入理解JavaScript中的闭包:原理、应用与代码演示
【10月更文挑战第12天】深入理解JavaScript中的闭包:原理、应用与代码演示