ES6 的解构赋值前每次都创建一个对象吗?会加重 GC 的负担吗?
本文来源于知乎上的一个提问。
为了程序的易读性,我们会使用 ES6 的解构赋值:
function f({a,b}){}
f({a:1,b:2});
这个例子的函数调用中,会真的产生一个对象吗?如果会,那大量的函数调用会白白生成很多有待 GC 释放的临时对象,那么就意味着在函数参数少时,还是需要尽量避免采用解构传参,而使用传统的:
function f(a,b){}
f(1,2);
上面的描述其实同时提了好几个问题:
- 会不会产生一个对象?
- 参数少时,是否需要尽量避免采用解构传参?
- 对性能(CPU/内存)的影响多大?
1. 从 V8 字节码分析两者的性能表现
首先从上面给的代码例子中,确实会产生一个对象。但是在实际项目中,有很大的概率是不需要产生这个临时对象的。
我之前写过一篇文章 使用 D8 分析 javascript 如何被 V8 引擎优化的。那么我们就分析一下你的示例代码。
function f(a,b){return a+b;
}const d = f(1, 2);
鉴于很多人没有 d8,因此我们使用 node.js 代替。运行:
node --print-bytecode add.js
其中的 --print-bytecode
可以查看 V8 引擎生成的字节码。在输出结果中查找 [generating bytecode for function: f]
:
[generating bytecode for function: ]
Parameter count 6
Frame size 320000003AC126862A @ 0 : 6e 00 00 02 CreateClosure [0], [0], #20000003AC126862E @ 4 : 1e fb Star r010 E> 0000003AC1268630 @ 6 : 91 StackCheck 98 S> 0000003AC1268631 @ 7 : 03 01 LdaSmi [1]0000003AC1268633 @ 9 : 1e f9 Star r20000003AC1268635 @ 11 : 03 02 LdaSmi [2]0000003AC1268637 @ 13 : 1e f8 Star r398 E> 0000003AC1268639 @ 15 : 51 fb f9 f8 01 CallUndefinedReceiver2 r0, r2, r3, [1]0000003AC126863E @ 20 : 04 LdaUndefined 107 S> 0000003AC126863F @ 21 : 95 Return
Constant pool (size = 1)
Handler Table (size = 16)
[generating bytecode for function: f]
Parameter count 3
Frame size 072 E> 0000003AC1268A6A @ 0 : 91 StackCheck 83 S> 0000003AC1268A6B @ 1 : 1d 02 Ldar a191 E> 0000003AC1268A6D @ 3 : 2b 03 00 Add a0, [0]94 S> 0000003AC1268A70 @ 6 : 95 Return
Constant pool (size = 0)
Handler Table (size = 16)
Star r0
将当前在累加器中的值存储在寄存器 r0
中。
LdaSmi [1]
将小整数(Smi)1
加载到累加器寄存器中。
而函数体只有两行代码:Ldar a1
和 Add a0, [0]
。
当我们使用解构赋值后:
[generating bytecode for function: ]
Parameter count 6
Frame size 24000000D24A568662 @ 0 : 6e 00 00 02 CreateClosure [0], [0], #2000000D24A568666 @ 4 : 1e fb Star r010 E> 000000D24A568668 @ 6 : 91 StackCheck 100 S> 000000D24A568669 @ 7 : 6c 01 03 29 f9 CreateObjectLiteral [1], [3], #41, r2100 E> 000000D24A56866E @ 12 : 50 fb f9 01 CallUndefinedReceiver1 r0, r2, [1]000000D24A568672 @ 16 : 04 LdaUndefined 115 S> 000000D24A568673 @ 17 : 95 Return
Constant pool (size = 2)
Handler Table (size = 16)
[generating bytecode for function: f]
Parameter count 2
Frame size 4072 E> 000000D24A568AEA @ 0 : 91 StackCheck 000000D24A568AEB @ 1 : 1f 02 fb Mov a0, r0000000D24A568AEE @ 4 : 1d fb Ldar r0000000D24A568AF0 @ 6 : 89 06 JumpIfUndefined [6] (000000D24A568AF6 @ 12)000000D24A568AF2 @ 8 : 1d fb Ldar r0000000D24A568AF4 @ 10 : 88 10 JumpIfNotNull [16] (000000D24A568B04 @ 26)000000D24A568AF6 @ 12 : 03 3f LdaSmi [63]000000D24A568AF8 @ 14 : 1e f8 Star r3000000D24A568AFA @ 16 : 09 00 LdaConstant [0]000000D24A568AFC @ 18 : 1e f7 Star r4000000D24A568AFE @ 20 : 53 e8 00 f8 02 CallRuntime [NewTypeError], r3-r474 E> 000000D24A568B03 @ 25 : 93 Throw 74 S> 000000D24A568B04 @ 26 : 20 fb 00 02 LdaNamedProperty r0, [0], [2]000000D24A568B08 @ 30 : 1e fa Star r176 S> 000000D24A568B0A @ 32 : 20 fb 01 04 LdaNamedProperty r0, [1], [4]000000D24A568B0E @ 36 : 1e f9 Star r285 S> 000000D24A568B10 @ 38 : 1d f9 Ldar r293 E> 000000D24A568B12 @ 40 : 2b fa 06 Add r1, [6]96 S> 000000D24A568B15 @ 43 : 95 Return
Constant pool (size = 2)
Handler Table (size = 16)
我们可以看到,代码明显增加了很多,CreateObjectLiteral
创建了一个对象。本来只有 2 条核心指令的函数突然增加到了近 20 条。其中不乏有 JumpIfUndefined
、CallRuntime
、Throw
这种指令。
- 扩展阅读:理解 V8 的字节码「译」
2. 使用 --trace-gc 参数查看内存
由于这个内存占用很小,因此我们加一个循环。
function f(a, b){return a + b;
}for (let i = 0; i < 1e8; i++) {const d = f(1, 2);
}console.log(%GetHeapUsage());
%GetHeapUsage()
函数有些特殊,以百分号(%)开头,这个是 V8 引擎内部调试使用的函数,我们可以通过命令行参数 --allow-natives-syntax
来使用这些函数。
node --trace-gc --allow-natives-syntax add.js
得到结果(为了便于阅读,我调整了输出格式):
[10192:0000000000427F50]
26 ms: Scavenge 3.4 (6.3) -> 3.1 (7.3) MB, 1.3 / 0.0 ms allocation failure[10192:0000000000427F50]
34 ms: Scavenge 3.6 (7.3) -> 3.5 (8.3) MB, 0.8 / 0.0 ms allocation failure4424128
当使用解构赋值后:
[7812:00000000004513E0]
27 ms: Scavenge 3.4 (6.3) -> 3.1 (7.3) MB, 1.0 / 0.0 ms allocation failure[7812:00000000004513E0]
36 ms: Scavenge 3.6 (7.3) -> 3.5 (8.3) MB, 0.7 / 0.0 ms allocation failure[7812:00000000004513E0]
56 ms: Scavenge 4.6 (8.3) -> 4.1 (11.3) MB, 0.5 / 0.0 ms allocation failure4989872
可以看到多了因此内存分配,而且堆空间的使用也比之前多了。使用 --trace_gc_verbose
参数可以查看 gc 更详细的信息,还可以看到这些内存都是新生代,清理起来的开销还是比较小的。
3. Escape Analysis 逃逸分析
通过逃逸分析,V8 引擎可以把临时对象去除。
还考虑之前的函数:
function add({a, b}){return a + b;
}
而这个 double
函数最终会被编译为
function double(x){return x + x;
}
在 V8 引擎内部,会按照如下步骤进行逃逸分析处理:
首先,增加中间变量:
function add(o){return o.a + o.b;
}function double(x) {let o = {a:x, b:x};return add(o);
}
把对函数 add
的调用进行内联展开,变成:
function double(x) {let o = {a:x, b:x};return o.a + o.b;
}
替换对字段的访问操作:
function double(x) {let o = {a:x, b:x};return x + x;
}
删除没有使用到的内存分配:
function double(x) {return x + x;
}
通过 V8 的逃逸分析,把本来分配到堆上的对象去除了。
4. 结论
不要做这种语法层面的微优化,引擎会去优化的,业务代码还是更加关注可读性和可维护性。如果你写的是库代码,可以尝试这种优化,把参数展开后直接传递,到底能带来多少性能收益还得看最终的基准测试。
举个例子就是 Chrome 49 开始支持 Proxy
,直到一年之后的 Chrome 62 才改进了 Proxy
的性能,使 Proxy
的整体性能提升了 24% ~ 546%。
原文发布时间为:2018年06月28日
原文作者:justjavac
本文来源:掘金 如需转载请与原作者联系
ES6 的解构赋值前每次都创建一个对象吗?会加重 GC 的负担吗?相关推荐
- ES6常用解构赋值有哪几种?
ES6常用解构赋值有哪几种? a.数组的解构赋值 //数组解析赋值,模式匹配 {let [a, b, c] = [1, 2, 3];console.log("模式匹配");cons ...
- ES6语法~解构赋值、箭头函数、class类继承及属性方法、map、set、symbol、rest、new.target、 Object.entries......
2015年6月17日 ECMAScript 6发布正式版本 前面介绍基本语法, 后面为class用法及属性方法.set.symbol.rest等语法. 一.基本语法: 1. 定义变 ...
- es6学习 -- 解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring). 以前,为变量赋值,只能直接指定值. let a = 1; let b = 2; let c ...
- ES6中解构赋值深入解读
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构 1.数组的解构赋值 let [head, ...tail] = [1, 2, 3, 4]; head // 1 tail ...
- ES6语法---解构赋值
解构赋值概念 按照一定的模式,从数组和对象中提取值,对变量进行赋值,就被称为解构. 目的是为了提高效率,使用起来更加方便. 以下的各个说明,我会类比着ES5去解释,希望能帮到小伙伴们. 数组解构 正常 ...
- 前端面试不用怕!一分钟带你了解es6的解构赋值
解构赋值(★★★)!!!!! ES6中允许从数组中提取值,按照对应位置,对变量赋值,对象也可以实现解构 <script>var stus=['李钟硕','刘诗诗','易烊千玺']//访问数 ...
- ES6:解构赋值及对象方法
解构赋值 对象解构 起别名:通过:来进行取名 //对象解构赋值 var obj = { uname: "张三", age: 21, sex: "男" } // ...
- 让java支持es6_简单看看es6解构赋值
哎,我真的是太难了,今天就被这个解构赋值(也可以叫做析构,貌似析构是在c++中的,所以我这里叫做解构赋值吧)弄的我很烦,本来以为很容易的,结果还是弄了好久...就总结一下解构吧! 1.解构的基本使用 ...
- 【ES6】变量的解构赋值
[ES6]变量的解构赋值 一.什么叫解构赋值? 二.解构赋值有哪些分类?写法? 1)对数组的解构赋值 2)对对象的解构赋值 3)对字符串的解构赋值 4)对数值和布尔值的解构赋值 5)对函数参数的解构赋 ...
最新文章
- 身为 Java 程序员必须掌握的 10 款开源工具!
- 非系统表空间损坏,rman备份恢复
- LUA 运行期间不独占线程的递归,通过回调实现
- python函数与函数式编程
- Java常见排序算法之直接选择排序
- 讲一下Asp.net core MVC2.1 里面的 ApiControllerAttribute
- 【FFMPEG源码终极解析】 av_packet_alloc 与 av_packet_free
- web前端入门学习(纯干货)
- 让电脑说话代码_让您的代码为您说话
- Oracle 项目就是那回事 ----表空间的管理
- Visual Studio BI 中维度的KeyColumns属性
- java ztree_ztree简介_动力节点Java学院整理
- 突发!Log4j 爆“核弹级”漏洞,Flink、Kafka等至少十多个项目受影响,微博、京东、网易等大厂都发起应急响应...
- 微信 - 微信语音转发好友 / 朋友圈方法
- html如何用表格做二级菜单栏,Excel表格制作二级下拉菜单步骤
- python数据拟合方法_Python-最小二乘法曲线拟合【转载】
- 开源项目的版权声明已无存在必要?
- 讲清楚之 javascript原形
- 常用存储器(SRAM、DRAM、NVRAM、PSRAM)简单介绍
- 《Presto(Trino)——The Definitive Guide》CHAPTER 6 Connectors Advanced CHAPTER 7 Connector Examples
热门文章
- java vo转map_javabean实体类对象转为Map类型对象的方法(转发)
- php redis list llen,redis llen list 命令简介
- python redis pipeline 堆积_Redis Pipeline python
- python可以用eclipse开发吗_Eclipse不是主要用来写Java么?Python也可以在eclipse上面写?Eclipse搭建Python开发环境...
- oracle导入dmp文件数据,dmp文件导入Oracle数据库
- 分片上传,断点续传,还有秒传
- matlab保存数据save,[转载]matlab中save,load使用方法
- python合并文件夹下的文件_Python实现合并同一个文件夹下所有PDF文件的方法示例...
- 读《学习JavaScript数据结构与算法》 第二章
- Android内容提供者(群发短信)