VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > temp > JavaScript教程 >
  • 面试官: 聊一聊Babel(2)

接收两个参数,一个是 ast 节点对象,一个是 visitor,visitor本质是挂载不同方法的 JavaScript 对象,visitor 也叫做访问者,顾名思义它会访问 ast 上每个节点,然后根据针对不同节点用相应的方法做出不同的转换.

const transformer = (ast) => {

    // 新 ast
    const newAst = {
        type: 'Program',
        body: []
    };

    // 在老 ast 上加一个指针指向新 ast
    ast._context = newAst.body;

    traverser(ast, {
        // 对于变量声明的处理方法
        VariableDeclaration: (node, parent) => {
            let functionDeclaration = {
                params: []
            };
            if (node.init.type === 'ArrowFunctionExpression') {
                functionDeclaration.type = 'FunctionDeclaration';
                functionDeclaration.identifierName = node.identifierName;
            }


            if (node.init.body.type === 'BinaryExpression') {
                functionDeclaration.body = {
                    type: 'BlockStatement',
                    body: [{
                        type: 'ReturnStatement',
                        argument: node.init.body
                    }],
                };
            }

            parent._context.push(functionDeclaration);
        },

        //对于字符的处理方法
        identifier: (node, parent) => {
            if (parent.type === 'ArrowFunctionExpression') {
            // 忽略我这暴力的操作....领略大意即可..
                ast._context[0].params.push({
                    type: 'identifier',
                    identifierName: node.identifierName
                });
            }
        }
    });

    return newAst;
};

生成代码(实现生成器generator)

我们之前提到过,生成代码这一步实际上是根据我们转换后的抽象语法树来生成新的代码,我们会实现一个函数, 他接受一个对象( ast),通过递归生成最终的代码

const generator = (node) => {
    switch (node.type) {
    // 如果是 `Program` 结点,那么我们会遍历它的 `body` 属性中的每一个结点,并且递归地
    // 对这些结点再次调用 codeGenerator,再把结果打印进入新的一行中。
    case 'Program':
        return node.body.map(generator)
            .join('\n');

    // 如果是FunctionDeclaration我们分别遍历调用其参数数组以及调用其 body 的属性
    case 'FunctionDeclaration':
        return 'function' + ' ' + node.identifierName + '(' + node.params.map(generator) + ')' + ' ' + generator(node.body);

    // 对于 `Identifiers` 我们只是返回 `node` 的 identifierName
    case 'identifier':
        return node.identifierName;

    // 如果是BlockStatement我们遍历调用其body数组
    case 'BlockStatement':
        return '{' + node.body.map(generator) + '}';

    // 如果是ReturnStatement我们调用其 argument 的属性
    case 'ReturnStatement':
        return 'return' + ' ' + generator(node.argument);
    
    // 如果是ReturnStatement我们调用其左右节点并拼接
    case 'BinaryExpression':
        return generator(node.left) + ' ' + node.operator + ' ' + generator(node.right);

    // 没有符合的则报错
    default:
        throw new TypeError(node.type);

    }
};

至此我们完成了一个简陋的微型 babel,我们开始试验:

const compiler = (input) => {
    const tokens = tokenizer(input);
    const ast =  parser(tokens);
    const newAst = transformer(ast);
    const output = generator(newAst);

    return output;
};

const str = 'const add = (a, b) => a + b';

const result = compiler(str);

console.log(result);
// function add(a,b) {return a + b}

我们成功地将一个es6的箭头函数转换为es5的function函数.

最后

我们可以通过这个微型 babel 了解 babel 的工作原理,如果让你对编译原理产生兴趣并去深入那是更好的, babel集合包 是有数十万行代码的巨大工程,我们用区区几百行代码只能展示其最基本的原理,代码有很多不合理之处,如果想真正的了解 babel 欢迎阅读器源码.

前端可以利用编译原理相关的东西还有很多,除了我们常见的es6转换工具 babel,代码检测的 eslint等等,我们还可以:

  1. 小程序多端转义 Taro
  2. 小程序热更新js 解释器
  3. babel与错误监控浏览器端 JavaScript 异常监控
  4. 模板引擎
  5. css 预处理后处理等等
  6. ...

相关教程