Skip to content
微信公众号

编译原理

Babel 简介

Babel 是 JavaScript 编译器(来自官网描述)。它将高版本 ECMAScript 语法编译为浏览器都支持的 ES5 语法。

Babel 毫无疑问是目前前端极其重要的基础设施之一了,在介绍 Babel 之前,我们简要梳理下 JavaScript 发展史。

JavaScript 发展简史

  • 1990 年底,万维网(WWW)诞生,可以在命令行查看网页。但通过命令行看网页,也是不太方便的。
  • 1992 年底,美国国家超级电脑应用中心(NCSA)开始开发一个独立的浏览器,叫做 Mosaic。这是人类历史上第一个浏览器,从此网页可以在图形界面的窗口浏览。
  • 1994 年 10 月,Mosaic 通信公司成立,不久后改名为 Netscape,其主要开发面向普通用户的新一代浏览器 Netscape Navigator。
  • 1994 年 12 月,Netscape Navigator 发布了 1.0 版。该版本很受欢迎,但缺乏一种脚本语言,用于控制浏览器的行为。
  • 1995 年,程序员 Brendan Eich 受雇于 Netscape 公司,只用了 10 天就开发出 JavaScript 1.0 版本,当时命名为 Mocha,1995 年 9 月改名为 LiveScript。
  • 1995 年 12 月,Netscape 公司与 Sun 公司(Java语言的发明者和所有者)达成协议,后者允许将 LiveScript 叫做 JavaScript。对于两个公司而言都有益处:NetScape 公司可以借助 Java 的声势,而 Sun 公司则将自己的影响力扩展到了浏览器。
  • 1996 年 3 月,Navigator 2.0 浏览器正式内置了 JavaScript 脚本语言。
  • 1996 年 8 月,微软模仿 JavaScript 开发了一种相近的语言,取名为 JScript,首先内置于 IE3.0。
  • 1996 年 11 月,Netscape 公司决定将 JavaScript 提交给国际标准化组织 ECMA(European Computer Manufacturers Association),希望 JavaScript 能够成为国际标准,以此抵抗微软。
  • 1997 年 7 月,ECMAScript 1.0 版发布。ECMA 组织发布 262 号标准文件(ECMA-262)的第一版,规定了浏览器脚本语言的标准,并将这种语言称为 ECMAScript。由于 ECMA 的开放和中立性,ECMAScript 和 JavaScript 的关系是,前者是后者的规格,后者是前者的实现。ECMA-262 标准后来也被另一个国际标准化组织 ISO(International Organization for Standardization)批准,标准号是 ISO-16262。
  • 1998 年 6 月,ECMAScript 2.0 版发布。
  • 1999 年 12 月,ECMAScript 3.0 版发布,成为 JavaScript 的通行标准,得到了广泛支持。
  • 2007 年 10 月,ECMAScript 4.0 版草案发布,但该版本过于激进,分歧很大。
  • 2008 年,Google 公司为 Chrome 浏览器而开发的 V8 编译器诞生。
  • 2009 年,Node.js 诞生。Node.js 的出现促进了前端工程化的快速发展,前端由石器时代快速进入了工业时代。
  • 2009 年 12 月,ECMAScript 5.0 版正式发布。截止 2012 年底,已得到绝大部分浏览器支持。
  • 2010 年,NPM、BackboneJS 和 RequireJS 的出现,标志着 JavaScript 进入模块化开发时代。
  • 2013 年 5 月,Facebook 发布 UI 框架库 React,引入了新的 JSX 语法,使得 UI 层可以用组件开发。
  • 2015 年 6 月,ECMAScript 6.0 版正式发布,并更名为: ECMAScript 2015 标准。
  • 2016 年 6 月,ECMAScript 2016 标准发布。
  • 2017 年 6 月,ECMAScript 2017 标准发布,正式引入了 async 函数,使得异步操作的写法出现了根本的变化。

ECMAScript 标准目前保持每年一次发布的速度更新,相应的,部分浏览器对标准的支持会显得有些滞后。

于是,基于对高版本语法转译为低版本语法的各种工具被开发了出来。

下一步,我们来了解下 JavaScript 引擎。

JavaScript 引擎

JavaScript 引擎是一个专门处理 JavaScript 脚本的虚拟机,负责解析 Javascript 语言。

对于浏览器而言,其内核包括:渲染引擎(layout engineer 或者 Rendering Engine)、JavaScript 引擎等。 渲染引擎负责取得网页的内容(HTML、XML、图像等等)、整理讯息(例如加入 CSS 等),以及计算网页的显示方式,然后会输出至显示器或打印机。 浏览器的内核的不同对于网页的语法解释会有不同,所以渲染的效果也不相同。所有网页浏览器、电子邮件客户端以及其它需要编辑、显示网络内容的应用程序都需要内核。

下面是一些 JavaScript 引擎:

  • SpiderMonkey,第一款 JavaScript 引擎,早期用于 Netscape Navigator,现时用于 Mozilla Firefox。
  • V8,开放源代码,由 Google 开发,是 Google Chrome 的一部分。
  • JavaScriptCore,开放源代码,用于 Safari。
  • Chakra (JScript 引擎),用于 Internet Explorer。
  • Chakra (JavaScript 引擎),用于 Microsoft Edge。

Babel

在了解了 JavaScript 及其引擎的各项背景后,我们来了解下 Babel。

Babel 是 JavaScript 编译器(来自官网描述)。它将高版本 ECMAScript 语法编译为浏览器都支持的 ES5 语法。

开发者编写的 JavaScript 代码,与浏览器等容器内运行的 JavaScript 通常是不同的,比如为了兼容低版本浏览器,需要将编写时代码转译为运行时代码。 举例:

js
// 这种语法,在 IE7 等低版本浏览器中是无法识别的,会报语法错误
const fn = (a, b) => a + b;

// 经过 Babel 及其插件编译为 ES5 后,IE7 等浏览器可以识别下面的代码
var fn = function(a, b) {
    return a + b;
};

Babel 发展史

  • 2014 年,Facebook 的澳大利亚的工程师 Sebastian McKenzie 发布了 6to5 这个库,用于将 ES6 转为 ES5,它使用的 AST 转换引擎 fork 自 acorn。
  • 2015 年 2 月 15 日,6to5 和 Esnext 的团队决定一起开发 6to5,并改名为 Babel,解析引擎改为 Babylon。后来,Babylon 移入到 @babel/parser。
  • 2015-03-31,Babel 5.0 发布。
  • 2015-10-30,Babel 6.0 发布。
  • Babel 自 6.0 起,就不再对代码进行修改。从这个版本开始,Babel 主要负责 Parse 和 Generate 流程,修改代码的 transform 过程全都交给插件去做。也就是说,Babel 只是一个语法解析器。
  • 2018-08-27,Babel 7.0 发布。

Babel 编译原理

Babel 本质上就是一个编译器,将一份代码编译为另一份代码。

Babel 的编译流程和大部分编译器的编译流程是相似的,包括三个过程:

  • 第一阶段:解析( Parsing )

解析 是将最初原始的代码转换为一种更加抽象的表示( AST )。

它包括:词法解析( Lexical Analysis )和语法解析( Syntactic Analysis )。

  • 第二阶段:转换( Transormation )

转换阶段会对 AST 进行遍历,在这个过程中对节点进行增删改查。

  • 第三阶段:重新生成代码( Code Generation )

编译器有很多种,我们先不考虑其他类型的编译器,先详细了解下 Babel 的整个编译过程。

编译过程如下图:

Babel 在编译过程用到了一些工具集,如下图:

本章,我们从最简单的案例入手理解编译原理,不去深究 Babel 各个模块的源码。

解析

解析 是将最初原始的代码转换为一种更加抽象的表示( AST )。

包括:词法解析( Lexical Analysis )和语法解析( Syntactic Analysis )。

  • 词法解析( Lexical Analysis )

词法解析器( Tokenizer ) 在这个阶段将字符串形式的代码转换为 Tokens (令牌),这个过程由词法解析器( Tokenizer 或 Lexer )完成。

令牌( Tokens )是扁平化的语法片段数组,每个数组项包含了:代码片段( value )、代码位置( start / end )、类型( type ) 等信息,这些信息有助于后续的语法分析。

如 n*n 经过词法解析后,转换成的令牌如下:

js
// n*n
[
{ type: { ... }, value: "n", start: 0, end: 1, loc: { ... } },
{ type: { ... }, value: "*", start: 2, end: 3, loc: { ... } },
{ type: { ... }, value: "n", start: 4, end: 5, loc: { ... } },
...
]

每一个 type 有一组属性来描述该令牌:

js
{
    type: {
        label: 'name',
        keyword: undefined,
        beforeExpr: false,
        startsExpr: true,
        rightAssociative: false,
        isLoop: false,
        isAssign: false,
        prefix: false,
        postfix: false,
        binop: null,
        updateContext: null   
    }
}

词法解析器( Tokenizer )本质上就是一个字符串处理方法,入参是字符串,返回结果是数组。

js
function Tokenizer() {
    const tokens = [];

    // 字符串解析,并将结果
    // processing

    // 返回 Token 数组
    return tokens;
}
  • 语法解析( Syntactic Analysis )

语法解析器( Parser ) 会把 Tokens 转换为抽象语法树( Abstract Syntax Tree, AST )。

AST 就是 JavaScript 中的一个 Object Tree,用于表示代码的语法结构。

举个例子:

js
const a = 1;
const b = 2;

console.log(a + b);

会被解析为:

其中,Program、VariableDeclarator、CallExpression 等表示节点类型,每个节点都是一个语法单元。

节点类型的属性描述了节点的详细信息。

JavaScript 中的节点类型非常多,再加上 JSX、Flow 等,我们不需要记忆它们,在需要的时候,ASTExplorer( https://astexplorer.net/ )可以帮助我们快速查看代码的 AST。

很显然,语法解析器接收 Token 数组,转为 AST,本质上是一个转换方法:

js
function parser(tokens) {
    const ast = {
        type: 'Program',
        body: []
    };

    // tokens to ast
    // processing

    return ast;
}

转换( Transformation )

这个过程 Babel 会对 AST 进行遍历,并且进行增删改查等转换动作。

Babel 所有插件都是在这个阶段工作, 比如语法转换、代码压缩等。

由于 AST 有众多类型的节点,在遍历 AST 过程中,需要用到 深度遍历 和 Visitor。

深度遍历,就是递归遍历 AST 对象;

Visitor,也就是访问者模式,它是一个对象,其 key 即为各个节点类型,值为各个处理方法。

转换过程如下:

js
// 转换器定义
function traverse(ast, visitor) {
    // 递归遍历
    // dfs(ast, visitor)
}

// 转换器执行
traverse(ast, {
    Program(node, parent) {
        // ...
    },

    CallExpression(node, parent) {
        // ...
    },

    NumberLiteral(node, parent) {
        // ...
    }
});

重新生成代码( Code Generation )

把 AST 转换回字符串形式的 Javascript,同时这个阶段还会生成 Source Map。

转换器接收一个 AST,并将其转为代码字符串。本质上是对象转字符串的方法。

js
function transformer(ast) {
    let code = '';

    // 遍历 AST,拼接 code 字符串

    return code;
}

本站总访问量次,本站总访客数人次
Released under the MIT License.