在github上看到一个相关题目:
a.b.c.d和a[‘b’][‘c’][‘d’],哪个性能更高
别看这题,题目上每个字都能看懂,但是里面涉及到的知识,暗藏杀鸡 这题要往深处走,会涉及ast抽象语法树、编译原理、v8内核对原生js实现问题
然后用ast的方式来解构代码:
对应于a.b.c.d:
{
"type": "Program",
"start": 0,
"end": 7,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 7,
"expression": {
"type": "MemberExpression",
"start": 0,
"end": 7,
"object": {
"type": "MemberExpression",
"start": 0,
"end": 5,
"object": {
"type": "MemberExpression",
"start": 0,
"end": 3,
"object": {
"type": "Identifier",
"start": 0,
"end": 1,
"name": "a"
},
"property": {
"type": "Identifier",
"start": 2,
"end": 3,
"name": "b"
},
"computed": false
},
"property": {
"type": "Identifier",
"start": 4,
"end": 5,
"name": "c"
},
"computed": false
},
"property": {
"type": "Identifier",
"start": 6,
"end": 7,
"name": "d"
},
"computed": false
}
}
],
"sourceType": "module"
}
对应于a[‘b’][‘c’][‘d’]:
{
"type": "Program",
"start": 0,
"end": 16,
"body": [
{
"type": "ExpressionStatement",
"start": 0,
"end": 16,
"expression": {
"type": "MemberExpression",
"start": 0,
"end": 16,
"object": {
"type": "MemberExpression",
"start": 0,
"end": 11,
"object": {
"type": "MemberExpression",
"start": 0,
"end": 6,
"object": {
"type": "Identifier",
"start": 0,
"end": 1,
"name": "a"
},
"property": {
"type": "Literal",
"start": 2,
"end": 5,
"value": "b",
"raw": "'b'"
},
"computed": true
},
"property": {
"type": "Literal",
"start": 7,
"end": 10,
"value": "c",
"raw": "'c'"
},
"computed": true
},
"property": {
"type": "Literal",
"start": 12,
"end": 15,
"value": "d",
"raw": "'d'"
},
"computed": true
}
}
],
"sourceType": "module"
}
这个题从AST角度看就很简单了,a[‘b’][‘c’][‘d’]和a.b.c.d,转换成AST前者的的树是含计算的,后者只是string literal,天然前者会消耗更多的计算成本,时间也更长。如果是按照直接解释运行的话,无非就是a[‘b’]的方式多了一个Literal的转换,实际上是基本没有什么区别
然后在github看到这题目的另一个描述:
[北京的月亮和上海的月亮, 哪个更圆?]
别看这题,题目上每个字都能看懂,但是里面涉及到的知识,暗藏杀鸡 这题要往深处走,会涉及天文学、气象学、地球经纬设计等底层知识
看到这儿,笑出猪笑,觉得上面的描述,也是暗藏杀鸡
上述题目通过AST解构,确实有一丢丢的性能差异。那么AST是啥?
在计算机科学中,ast全称为:抽象语法树(Abstract Syntax Tree,AST),是源代码语法结构的一种抽象表示。它以树状的形式表现编程语言的语法结构,树上的每个节点都表示源代码中的一种结构。
很多语言有AST结构编译语言,例如Java、JavaScript、Python。
这边介绍下JavaScript的ast抽象树:
用个例子:
function add(a, b) {
return a + b
}
首先,我们拿到的这个语法块,是一个FunctionDeclaration(函数定义)对象。
用力拆开,它成了三块:
一个id,就是它的名字,即add
两个params,就是它的参数,即[a, b]
一块body,也就是大括号内的一堆东西
add没办法继续拆下去了,它是一个最基础Identifier(标志)对象,用来作为函数的唯一标志,就像人的姓名一样。
{
name: 'add'
type: 'identifier'
...
}
params继续拆下去,其实是两个Identifier组成的数组。之后也没办法拆下去了。
[
{
name: 'a'
type: 'identifier'
...
},
{
name: 'b'
type: 'identifier'
...
}
]
接下来,我们继续拆开body
我们发现,body其实是一个BlockStatement(块状域)对象,用来表示是{return a + b}
打开Blockstatement,里面藏着一个ReturnStatement(Return域)对象,用来表示return a + b
继续打开ReturnStatement,里面是一个BinaryExpression(二项式)对象,用来表示a + b
继续打开BinaryExpression,它成了三部分,left,operator,right
operator 即+
left 里面装的,是Identifier对象 a
right 里面装的,是Identifer对象 b
就这样,我们把一个简单的add函数拆解完毕,用图表示就是:
那么,上面我们提到的Identifier、Blockstatement、ReturnStatement、BinaryExpression, 这一个个小部件的说明书去哪查?
请查看AST对象文档
ES6 转 ES5 目前行业标配是用 Babel,转换的大致流程如下跟AST就有关系:
解析:解析代码字符串,生成 AST;
转换:按一定的规则转换、修改 AST;
生成:将修改后的 AST 转换成普通代码
结语:其实本码农并不想了解这些,只是行走在各种博文之间的搬运工。分享给各位读者