第3题-ES6代码转化为ES5的思路及实践

面试题目:

ES6 代码转成 ES5 代码的实现思路是什么?

示例:

// 转换前
const fn = () => {
    console.log('前端名狮');
};

// 转换后
"use strict";
var fn = function fn() {
    console.log('前端名狮');
};

答案解析:

ES6作为JS的新规范,加入了很多新的语法和API,而市面上的浏览器并没有全部兼容,所以需要将ES6语法代码转为ES5的代码。

es6比es5多出来的部分可分为两类:

  1. 一类是语法,如箭头函数,解构;
  2. 一类是新的类、新的类方法、新的实例方法,如:Promise, Array.from, Array.prototype.find

转换语法时,一般是在抽象语法树层转换代码,如上文示例中的箭头函数。

对于新的类、类方法,实例方法,基本就是用polyfill,或者polyfill加上代码转换。

例如:Array.from,只需要使用es5的语法,自己实现一遍Array.from,就可以用不改动源代码而使用Array.from这个es6的api了。

ES6 转 ES5 目前行业标配是用 Babel,转换的大致流程如下:

  1. 解析:解析代码字符串,生成 AST;
  2. 转换:按一定的规则转换、修改 AST;
  3. 生成:将修改后的 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"
}

结论:

  1. result.json 就是修改后生成的语法树,json格式。
  2. 语法树中的kind的值由let被替换为var

知识点延伸:

Acorn

acorn是常用的一个 JS 解析器,常用到的还有一个 Esprima,acorn在它之后诞生,两者相比,acorn实现代码更少,速度和Esprima相差无几。

二者都遵循 The Estree Spec 规范,也就是得到的结果在很大部分上是兼容的。对 ECMAScript 来说,社区有一个 AST 规范::ESTree Spec

解析 javascript 的三件套: acornacorn-walkescodegen

AST 节点是按照如下的格式定义的:

interface Node {
    type: string;
    loc: SourceLocation | null;
}

应用举例:

  1. webpack是使用acorn作为自己的Parser的基础库。

  2. babel, babylon.js也是fork的acorn实现的.

最后推荐一个在线实时查看AST的网站:https://astexplorer.net/


扫一扫 下方二维码,关注我的公众号【前端名狮】,更多精彩内容陪伴你!

【前端名狮】

你可能感兴趣的:(第3题-ES6代码转化为ES5的思路及实践)