Daily-Interview-Question
Daily-Interview-Question copied to clipboard
第 66 题:ES6 代码转成 ES5 代码的实现思路是什么
将ES6的代码转换为AST语法树,然后再将ES6 AST转为ES5 AST,再将AST转为代码
题目说的是 ES6,所以不考虑 .jsx
、.ts
这类 js 拓展语言。
ES6 转 ES5 目前行业标配是用 Babel
,转换的大致流程如下:
- 解析:解析代码字符串,生成 AST;
- 转换:按一定的规则转换、修改 AST;
- 生成:将修改后的 AST 转换成普通代码。
如果不用工具,纯人工的话,就是使用或自己写各种 polyfill 了。
把 ES6 代码转成 ES5 代码的实现思路可以分成三步:
- 打开冰箱
- 把大象装进去
- 关上冰箱
喔,不对,原谅我开了个玩笑,嗯,有点冷……
回到正题上来,说到 ES6 代码转成 ES5 代码,我们肯定会想到 Babel。所以,我们可以参考 Babel 的实现方式。
那么 Babel 是如何把 ES6 转成 ES5 呢,其大致分为三步:
- 将代码字符串解析成抽象语法树,即所谓的 AST
- 对 AST 进行处理,在这个阶段可以对 ES6 代码进行相应转换,即转成 ES5 代码
- 根据处理后的 AST 再生成代码字符串
基于此,其实我们自己就可以实现一个简单的“编译器”,用于把 ES6 代码转成 ES5。
比如,可以使用 @babel/parser
的 parse
方法,将代码字符串解析成 AST;使用 @babel/core
的 transformFromAstSync
方法,对 AST 进行处理,将其转成 ES5 并生成相应的代码字符串;过程中,可能还需要使用 @babel/traverse
来获取依赖文件等。对此感兴趣的可以看看这个。
如果有误,希望可以帮忙指出来,多谢。
说说我的理解哦 es6比es5多出来的部分分两类 一类是语法,如箭头函数,解构; 一类是新的类、新的类方法、新的实例方法,如:Promise, Array.from, Array.prototype.find
babel在做语法转换的时候,通过抽象语法树来实现代码层面的翻译。 比如将 const fn = () => { Array.isArray([1, 2, 3]); }; 转成 "use strict"; var fn = function fn() { Array.isArray([1, 2, 3]); };
对于新的类、类方法,实例方法,基本就是polyfill,或者polyfill加上代码转换。 拿Array.from来说,只需要使用es5的语法,自己实现一遍Array.from,就可以用不改动源代码而使用Array.from这个es6的api了,@babel/polyfill这个包就是做这事儿的。 复杂点的Promise在babel转译的时候,不仅会调整源代码,还需要引入@babel/polyfill。有时候会遇到regeneratorRuntime is not defined这个报错,原因是源代码中Promise部分被转译了,转译后用到的regeneratorRuntime在@babel/polyfill中,但@babel/polyfill没被引入。
ES6 代码转换成 ES5 的思路大家都讲的差不多,我来做个补充。
Babel 将把 ES6 转成 ES5 第二步,从 Babel 6.0 开始,就不再对代码进行转换。现在 Babel 只负责parse 和 generate 流程,也就是专注于解析和生成阶段。转换代码的 transform 过程全都交给bable插件去做了。
我们的项目里的 .babelrc
文件就是用来配置babel处理的,常有plugins 与 presets
配置项,当他们同时存在的时候,先执行plugins
从上到下,在执行presets
从左到右。
借用babel转换思想,将es6解析成AST,然后按照统一规则转换修改AST,再将AST解析成es5
涉及到的主要是AST相关的内容,AST(Abstract Syntax Tree)中文叫抽象语法树,是用来表示源代码语法的一种树形结构,树上的每个节点都代表源代码的一种结构。AST在我们日常应用中非常广泛,我们的代码高亮,代码检查等都是依靠的AST。
那么ES6转ES5的思路,其实就是在处理AST的过程中进行操作。转化代码的流程一般分为三步
- 将代码通过解释器转化为AST,可以通过 astexplorer 来查看代码对应的AST结构
- 通过一定的规则,去修改AST的结构(常见的比如转jsx,ES6转ES5都是在这一步进行操作)
- 将修改后的AST转化为普通代码
现在一般使用的就是bable转ES6,具体的ES6转ES5在第二步中的逻辑,那就得看bable中转ES6的babel-preset-es2015
对AST进行操作的源码了
这问的是问题么? 你说的是普通开发者的思路? 还是babel作者的思路啊??
ES6转ES5分为以下两种情况
1.语法转换
ES6语法通过babel等工具为ES5语法,本质是将ES6语法转AST(抽象语法数——对编程语言编写的程序的一种描述)再将AST转为ES5语法代码;例如:let,const转换为var,箭头函数转换为function函数声明等
2.API转换
采用babel-polyfill等工具对ES5中不存在的API(包括Set等ES6中新的数据结构)做修复,例如:Array.prototype.includes
Set
Map
等在ES5中不存在,需要用相应的ES5代码实现这些API
补充说明
- .vue文件通过webpack的vue-loader分析出
script
style
template
再走上面的ES6转ES5流程 - jsx通过babel插件转js语法再走ES6转ES5
- ts通过tsc结合tsconfig.json直接转ES5
ES6 代码转成 ES5 代码的实现思路是什么
- 把代码字符串解析成AST(抽象语法树)
- AST就是一个json,按照一定规则把这个json里ES6部分的东西转换成ES5的
- 再把修改后的AST转换成代码
关于AST
AST的全称是abstract syntax tree,中文名叫抽象语法树,假设有一个函数,
function hello(a, b, c){
}
这段代码被解析成的ast树就是
type: Program
-
body
-
#1
type: FunctionDeclaration
-
id
type: Identifier
name: hello
-
params
-
#1
type: Identifier
name: a
-
#2
type: Identifier
name: b
-
#3
type: Identifier
name: c
-
body
type: BlockStatement
body
generator: false
expression: false
async: false
sourceType: script
这样看起来还不是很直观,其实上面的树就等于
{
"type": "Program",
"body": [
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "hello"
},
"params": [
{
"type": "Identifier",
"name": "a"
},
{
"type": "Identifier",
"name": "b"
},
{
"type": "Identifier",
"name": "c"
}
],
"body": {
"type": "BlockStatement",
"body": []
},
"generator": false,
"expression": false,
"async": false
}
],
"sourceType": "script"
}
有了这个json后,就可以对代码进行操作了。在没有树的情况下,如果要对文件里的某一个语句进行替换的话,一般就是全局 搜索然后replace,这样有可能影响到别的代码,但是有了树后,就可以对这个json进行操作,精确地去修改某个对象的属性,也就不会影响到别的代码了。所以babel转换ES6的核心,就是在ast中按照一定的规则取修改json里的属性和方法,然后再把tree转换成代码
使用babel
- es6 代码字符串解析生成 AST
- 按照es5 的规则 转换 生成符合es5规范的 AST
- 将 AST 转换为代码字符串