网站首页 > 技术文章 正文
我们都知道 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 (https://babeljs.io/) 这个网站上看看 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: https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-function.prototype.apply
猜你喜欢
- 2024-11-16 3.1 Python高级编程-函数式编程工具
- 2024-11-16 面向对象编程的一些思考(面向对象编程的理解)
- 2024-11-16 知识总结-Java8 Stream函数式编程
- 2024-11-16 bind、call、apply 区别?如何实现一个bind?
- 2024-11-16 Javascript基础重拾笔记之手写apply、call
- 2024-11-16 java 8新特性 常用内置函数式接口
- 2024-11-16 7、JavaScript 内置的常用对象有哪些?该对象常用的方法(必会)
- 2024-11-16 Java中“::”是什么含义(java中+是什么)
- 2024-11-16 Java 8 中的 Function:让代码从繁琐到简洁的魔法工具
- 2024-11-16 13万字详细分析JDK中Stream的实现原理(中)
- 标签列表
-
- content-disposition (47)
- nth-child (56)
- math.pow (44)
- 原型和原型链 (63)
- canvas mdn (36)
- css @media (49)
- promise mdn (39)
- readasdataurl (52)
- if-modified-since (49)
- css ::after (50)
- border-image-slice (40)
- flex mdn (37)
- .join (41)
- function.apply (60)
- input type number (64)
- weakmap (62)
- js arguments (45)
- js delete方法 (61)
- blob type (44)
- math.max.apply (51)
- js (44)
- firefox 3 (47)
- cssbox-sizing (52)
- js删除 (49)
- js for continue (56)
- 最新留言
-