面试题目:
ES6 代码转成 ES5 代码的实现思路是什么?
示例:
// 转换前
const fn = () => {
console.log('前端名狮');
};
// 转换后
"use strict";
var fn = function fn() {
console.log('前端名狮');
};
答案解析:
ES6作为JS的新规范,加入了很多新的语法和API,而市面上的浏览器并没有全部兼容,所以需要将ES6语法代码转为ES5的代码。
es6比es5多出来的部分可分为两类:
- 一类是语法,如箭头函数,解构;
- 一类是新的类、新的类方法、新的实例方法,如:
Promise
,Array.from
,Array.prototype.find
转换语法时,一般是在抽象语法树层转换代码,如上文示例中的箭头函数。
对于新的类、类方法,实例方法,基本就是用polyfill
,或者polyfill
加上代码转换。
例如:
Array.from
,只需要使用es5的语法,自己实现一遍Array.from,就可以用不改动源代码而使用Array.from这个es6的api了。
ES6 转 ES5 目前行业标配是用 Babel,转换的大致流程如下:
- 解析:解析代码字符串,生成 AST;
- 转换:按一定的规则转换、修改 AST;
- 生成:将修改后的 AST 转换成普通代码。
下面来点干货,按照上面三个步骤
实现:将 let
转换为 var
。
// example.js
let a = 3;
let b = 2;
console.log(a + b);
// app.js
const fs = require('fs');
const acorn = require('acorn'); //将JS代码转化为语法树模块
const walk = require('acorn-walk'); //JS语法树遍历各节点
const escodegen = require('escodegen'); //将JS语法树反编译成js代码模块
// 1. 获取JS代码
let code = fs.readFileSync('./example.js');
// 2. 用acorn将代码解析为语法树ast
let ast = acorn.parse(code, {
ranges: true
});
// 3. 用walk操作语法树ast,输出node.value
walk.simple(ast, {
VariableDeclaration(node) {
if(node.kind === 'let'){
node.kind = 'var'; // 把let 变成 var
}
}
})
fs.writeFileSync('result.json', JSON.stringify(ast)); // 将修改后的语法树ast存储为result.json文件
fs.writeFileSync('result.js', escodegen.generate(ast, {comment: true})); // 用escodegen将语法树转换为最终代码,并存储为result.js
输出结果
// result.js
var a = 3;
var b = 2;
console.log(a + b);
// result.json 传说中的语法树
{
"type": "Program",
"start": 0,
"end": 44,
"range": [0, 44],
"body": [{
"type": "VariableDeclaration",
"start": 0,
"end": 10,
"range": [0, 10],
"declarations": [{
"type": "VariableDeclarator",
"start": 4,
"end": 9,
"range": [4, 9],
"id": {
"type": "Identifier",
"start": 4,
"end": 5,
"range": [4, 5],
"name": "a"
},
"init": {
"type": "Literal",
"start": 8,
"end": 9,
"range": [8, 9],
"value": 3,
"raw": "3"
}
}],
"kind": "var"
}, {
"type": "VariableDeclaration",
"start": 13,
"end": 23,
"range": [13, 23],
"declarations": [{
"type": "VariableDeclarator",
"start": 17,
"end": 22,
"range": [17, 22],
"id": {
"type": "Identifier",
"start": 17,
"end": 18,
"range": [17, 18],
"name": "b"
},
"init": {
"type": "Literal",
"start": 21,
"end": 22,
"range": [21, 22],
"value": 2,
"raw": "2"
}
}],
"kind": "var"
}, {
"type": "ExpressionStatement",
"start": 25,
"end": 44,
"range": [25, 44],
"expression": {
"type": "CallExpression",
"start": 25,
"end": 43,
"range": [25, 43],
"callee": {
"type": "MemberExpression",
"start": 25,
"end": 36,
"range": [25, 36],
"object": {
"type": "Identifier",
"start": 25,
"end": 32,
"range": [25, 32],
"name": "console"
},
"property": {
"type": "Identifier",
"start": 33,
"end": 36,
"range": [33, 36],
"name": "log"
},
"computed": false
},
"arguments": [{
"type": "BinaryExpression",
"start": 37,
"end": 42,
"range": [37, 42],
"left": {
"type": "Identifier",
"start": 37,
"end": 38,
"range": [37, 38],
"name": "a"
},
"operator": "+",
"right": {
"type": "Identifier",
"start": 41,
"end": 42,
"range": [41, 42],
"name": "b"
}
}]
}
}],
"sourceType": "script"
}
结论:
-
result.json
就是修改后生成的语法树,json格式。 - 语法树中的
kind
的值由let
被替换为var
。
知识点延伸:
Acorn
acorn
是常用的一个 JS 解析器,常用到的还有一个 Esprima
,acorn在它之后诞生,两者相比,acorn
实现代码更少,速度和Esprima
相差无几。
二者都遵循 The Estree Spec
规范,也就是得到的结果在很大部分上是兼容的。对 ECMAScript
来说,社区有一个 AST 规范::ESTree Spec
解析 javascript 的三件套: acorn
、acorn-walk
、escodegen
。
AST 节点是按照如下的格式定义的:
interface Node {
type: string;
loc: SourceLocation | null;
}
应用举例:
webpack
是使用acorn
作为自己的Parser
的基础库。babel
,babylon.js
也是fork的acorn
实现的.
最后推荐一个在线实时查看AST的网站:https://astexplorer.net/
扫一扫 下方二维码,关注我的公众号【前端名狮】,更多精彩内容陪伴你!