深入理解babel的运行原理

1,299 阅读5分钟
  • babel介绍

babel是目前最流行的JavaScript编译器,最常见的是将es6,es7等语法解析成低端浏览器能够识别的语法(向下兼容)

  • babel原理

解析(parse),转换(transform),生成(generate)

说到babel原理我就想到了vue template模版编译原理了
也就是使用了babel针对vue模版字符串进行解析。
原理:解析 ==> 优化 ==> 生成
解析:将vue模版字符串解析成ast树。
优化:找出那些静态节点和静态根节点并打上标记,避免下次重复更新。
生成:使用Ast生成render函数代码。
  • 解析器
    • 词法环境

      • 参数分析

          1:函数的在运行的瞬间,生成一个活动对象(Active Object)就是所谓的AO
          2:函数接收参数,添加到AO的属性上面,值全部都是undefine,如AO.age=undefine
          3:接收实参,形成AO对应的属性值
          参考用例:
          function fn(a){
              console.log(a);
          }
          /*分析函数参数*/
          // 将函数的形参添加为AO属性,属性的默认值为undefined。
          fn();// undefined
          // 接收函数的实参,并覆盖原属性值。
          fn(1);// 1
        
      • 变量分析

          分析变量声明/分析局部变量:
          1:若AO中不存在与声明的变量所对应的属性,则添加AO属性为undefined
          2:若AO中已存在与声明的变量所对应的属性,则不做任何修改。
          参考用例:
          function fn(a){
              console.log(a);// 1
              /*分析变量声明/分析局部变量*/
              // 若AO中已存在与声明的变量所对应的属性,则不做任何修改。
              var a = 100;// 执行过程:对变量进行赋值
              console.log(a);// 100
          }
          fn(1);
          AO = {}
          分析函数参数
          AO = {a:undefined}
          AO = {a:1}
          分析局部变量
          AO = {a:1}
          运行阶段赋值
          AO = {a:100}
        
      • 函数分析

          分析函数的声明:
          若AO中存在与函数名所对应的属性,则覆盖原属性为一个函数表达式。
          参考用例:
          function fn(a){
              console.log(a);// a(){}
              /*分析变量声明/分析局部变量*/
              // 若AO中已存在与声明的变量所对应的属性,则不做任何修改。
              var a = 100;// 执行过程:对变量进行赋值
              console.log(a);// 100
              /*分析函数声明*/
              // 若AO中存在与函数名所对应的属性,则覆盖原属性为一个函数表达式。
              function a(){}
              console.log(a); // 100
          }
          fn(1);
          AO = {}
          分析函数参数
          AO = {a:undefined}
          AO = {a:1}
          分析局部变量
          AO = {a:1}
          运行阶段赋值
          AO = {a:100}
          分析函数声明
          AO = {a:function(){}}
          函数声明提升优先高于变量声明提升
          总结:作用域链就是函数由内向外所产生的活动对象(AO)的链
        
    • 词法分析

        词法分析是将字符序列转换为单词(Token)序列的过程
        参考用例:
            let arr= '我是一个词法分析';
            let obj = {}
            转换成token之后:
            [
                {
                    "type": "Keyword",
                    "value": "let"
                },
                {
                    "type": "Identifier",
                    "value": "arr"
                },
                {
                    "type": "Punctuator",
                    "value": "="
                },
                {
                    "type": "String",
                    "value": "'我是一个词法分析'"
                },
                {
                    "type": "Punctuator",
                    "value": ";"
                },
                {
                    "type": "Keyword",
                    "value": "let"
                },
                {
                    "type": "Identifier",
                    "value": "obj"
                },
                {
                    "type": "Punctuator",
                    "value": "="
                },
                {
                    "type": "Punctuator",
                    "value": "{"
                },
                {
                    "type": "Punctuator",
                    "value": "}"
                }
            ]
      
    • 语法分析

      语法分析是编译过程的一个逻辑阶段。语法分析的任务是在词法分析的基础上将单词序列组合成语法树
        语法分析用例:
        {
          "type": "Program",
          "body": [
            {
              "type": "VariableDeclaration",
              "declarations": [
                {
                  "type": "VariableDeclarator",
                  "id": {
                    "type": "Identifier",
                    "name": "arr"
                  },
                  "init": {
                    "type": "Literal",
                    "value": "我是一个词法分析",
                    "raw": "'我是一个词法分析'"
                  }
                }
              ],
              "kind": "let"
            },
            {
              "type": "VariableDeclaration",
              "declarations": [
                {
                  "type": "VariableDeclarator",
                  "id": {
                    "type": "Identifier",
                    "name": "obj"
                  },
                  "init": {
                    "type": "ObjectExpression",
                    "properties": []
                  }
                }
              ],
              "kind": "let"
            }
          ],
          "sourceType": "script"
        }
      
        应该能看懂,不需要详细讲解
      

      AST在线转换工具:esprima.org/demo/parse.…

  • 转换
    • 实践babel

      • 搭建项目工程

        1.新建文件夹:npm init初始化项目
        2.新建.babelrc文件:
        {
          "presets": [],
          "plugins": []
        }
        存放在项目的根目录下,该文件用来设置转码规则和插件
        3.安装需要的转码包:
        //ES2015转码规则
        npm install --save-dev babel-preset-es2015
        ES7不同阶段语法提案的转码规则(共有4个阶段),选装一个
        npm install --save-dev babel-preset-stage-0
        npm install --save-dev babel-preset-stage-1
        npm install --save-dev babel-preset-stage-2
        npm install --save-dev babel-preset-stage-3
        4.配置.babelrc文件
        {
            "presets": [
              "es2015",
              "react",
              "stage-2"
            ],
            "plugins": []
        }
        5.安装babel提供的命令行工具:
        npm install --save-dev babel-cli
        6.修改package.json脚本命令:
        "scripts": {
            "build": "babel src -d lib"
        },
        7.执行npm run build (一个简易的babel转码项目搭建成功)
        

        参考搭建地址:www.ruanyifeng.com/blog/2016/0…

      • 转码实践(transform)

        调用Babel的API进行转码,就要使用babel-core模块
        安装下:npm install babel-core --save
        参考用例:
        var babel = require('babel-core');
        // 字符串转码
        babel.transform('code();', options);
        // => { code, map, ast }
        // 文件转码(异步)
        babel.transformFile('filename.js', options, function(err, result) {
          result; // => { code, map, ast }
        });
        // 文件转码(同步)
        babel.transformFileSync('filename.js', options);
        // => { code, map, ast }
        // Babel AST转码
        babel.transformFromAst(ast, code, options);
        // => { code, map, ast }
        
        transform转码用例:
        var babel = require('babel-core')
        var es6Code = 'let x = n => n + 1';
        var es5Code = babel.transform(es6Code, {
            presets: ['es2015']
          }).code;
        输出转码后的js:
        "use strict";
        var x = function x(n) {
          return n + 1;
        };
        
      • 拆分详解:

        parser:(输入源码,输出抽象语法树ast)
        npm install --save-dev @babel/parser
        var babel = require('@babel/parser');
        var es6Code = 'let x = n => n + 1';
        var ast = BabelParser.parse(es6Code);
        
        transform:(结合babel preset,plugin,转换上述ast,生成新的ast)
        npm install --save-dev @babel/traverse
        var traverse = require('@babel/traverse')
        var new_ast = traverse(ast, {
          FunctionDeclaration(path) {
            const node = path.node
            // 获取函数名称等
            path.replaceWith()//替换为新的节点
            path.remove() // 删除当前节点
            path.skip() 跳过子节点
            let copyNode = t.cloneNode(node)//复制当前节点
            traverse(copyNode, {}, {}, path)//遍历子树,持续上述操作
          }
        })
        
        generate:(根据新的语法树ast,生成编译后新的代码)
        npm install --save-dev @babel/generator
        var generator = require('@babel/generator')
        var newCode = generate(new_ast);
        //输出转码后的代码
        // 实际上,babel的转换过程就是构建和修改抽象语法树的过程
        

上述所有总结:解析 => 转换 => 生成

babel运行机制:
1.经过词法分析
2.经过语法分析
3.得到AST抽象语法树
4.经过babel插件实现递归转换
5.最终得到转换之后的AST抽象语法树
6.通过generate重新将新AST转换成代码输出

** 个人理解笔记文档,勿喷 **