这是我参与「第五届青训营」伴学笔记创作活动的第 8 天
开新坑开新坑!
因为最近的事需要把js源码解析,需要一个能把js变AST的玩意,于是就找到了recast
不知道大家有没有一个疑问,为什么js和java在运行上总是不太一样的,这是因为:
静态语言:比如C++、Go等,都需要提前编译 (AOT) 成机器码然后执行,这个过程主要使用编译器来完成;
动态语言:比如JavaScript、Python等,只在运行时进行编译执行 (JIT) ,这个过程通过解释器完成;
当然js的运行方法已经很多样了,不同场合的执行方法不太一样,所以现在也不是特别绝对的第二种
代码毫无疑问本质是字符串,或者说是字符流,想要解析代码的第一步当然是把代码解析为一个个不能再拆开的独立的个体,比如let,new这样的独立个体,第二步是将这些独立个体组成一个整体,也就是ast 抽象语法树,这个过程一般是浏览器的Parser的工作。
在Chrome中开始下载Javascript文件后,Parser就会开始并行在单独的线程上解析代码。生成AST的过程可以分为词义分析和语义分析两个过程。
词义分析
这部分就是上面加粗第一步的工作: 把代码解析为独立个体
比如:
let m=10
解析完成之后就成了
{ type: 'Keyword', value: 'let', loc: [Object] },
{ type: 'Identifier', value: 'm', loc: [Object] },
{ type: 'Punctuator', value: '=', loc: [Object] },
{ type: 'Numeric', value: '1', loc: [Object] }
也就是把他拆成了一个一个独立的最小个体了
语义分析
只有上面那些肯定是不够的,因为代码的关系是复杂的嵌套的,不应该是一个平面的一维数组来解释的。
语义分析就是把上面的变成一个树状结构,比如还是
let m=10
在解析过后,就会变成:
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "m"
},
"init": {
"type": "Literal",
"value": 1,
"raw": "1"
}
}
],
"kind": "let"
}
],
"sourceType": "script"
}
这里记录了变量名,值,以及声明变量的类型
我们再看一个什么都不干的循环:
for(let m=0;m<10;m++){}
在词义分析后,他变成了:
在语义分析后,他变成了:
{
"type": "Program",
"body": [
{
"type": "ForStatement",
"init": {
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "m"
},
"init": {
"type": "Literal",
"value": 0,
"raw": "0"
}
}
],
"kind": "let"
},
"test": {
"type": "BinaryExpression",
"operator": "<",
"left": {
"type": "Identifier",
"name": "m"
},
"right": {
"type": "Literal",
"value": 10,
"raw": "10"
}
},
"update": {
"type": "UpdateExpression",
"operator": "++",
"argument": {
"type": "Identifier",
"name": "m"
},
"prefix": false
},
"body": {
"type": "BlockStatement",
"body": []
}
}
],
"sourceType": "script"
}