【图文详解】200行JS代码,带你实现代码编译器(人人都能学会) 上

简介: 【图文详解】200行JS代码,带你实现代码编译器(人人都能学会) 上


网络异常,图片无法展示
|

最近看到掘金、前端公众号好多 ES2020 的文章,想说一句:放开我,我还学得动!

先问大家一句,日常项目开发中你能离开 ES6 吗?


一、前言

对于前端同学来说,编译器可能适合神奇的魔盒🎁,表面普通,但常常给我们惊喜。

编译器,顾名思义,用来编译,编译什么呢?当然是编译代码咯🌹。

网络异常,图片无法展示
|

其实我们也经常接触到编译器的使用场景:

  • React 中 JSX 转换成 JS 代码;
  • 通过 Babel 将 ES6 及以上规范的代码转换成 ES5 代码;
  • 通过各种 Loader 将 Less / Scss 代码转换成浏览器支持的 CSS 代码;
  • 将 TypeScript 转换为 JavaScript 代码。
  • and so on...

使用场景非常之多,我的双手都数不过来了。😄

虽然现在社区已经有非常多工具能为我们完成上述工作,但了解一些编译原理是很有必要的。接下来进入本文主题:200行JS代码,带你实现代码编译器


二、编译器介绍

2.1 程序运行方式

现代程序主要有两种编译模式:静态编译和动态解释。推荐一篇文章《Angular 2 JIT vs AOT》介绍得非常详细。

静态编译

简称 AOT(Ahead-Of-Time)即 提前编译 ,静态编译的程序会在执行前,会使用指定编译器,将全部代码编译成机器码。

网络异常,图片无法展示
|

(图片来自:segmentfault.com/a/119000000…


在 Angular 的 AOT 编译模式开发流程如下:

  • 使用 TypeScript 开发 Angular 应用
  • 运行 ngc 编译应用程序
  • 使用 Angular Compiler 编译模板,一般输出 TypeScript 代码
  • 运行 tsc 编译 TypeScript 代码
  • 使用 Webpack 或 Gulp 等其他工具构建项目,如代码压缩、合并等
  • 部署应用

动态解释

简称 JIT(Just-In-Time)即 即时编译 ,动态解释的程序会使用指定解释器,一边编译一边执行程序。

网络异常,图片无法展示
|
(图片来自: segmentfault.com/a/119000000…


在 Angular 的 JIT 编译模式开发流程如下:

  • 使用 TypeScript 开发 Angular 应用
  • 运行 tsc 编译 TypeScript 代码
  • 使用 Webpack 或 Gulp 等其他工具构建项目,如代码压缩、合并等
  • 部署应用

AOT vs JIT

AOT 编译流程:

网络异常,图片无法展示
|
(图片来自: segmentfault.com/a/119000000…

JIT 编译流程:

网络异常,图片无法展示
|
(图片来自: segmentfault.com/a/119000000…

特性 AOT JIT
编译平台 (Server) 服务器 (Browser) 浏览器
编译时机 Build (构建阶段) Runtime (运行时)
包大小 较小 较大
执行性能 更好 -
启动时间 更短 -

除此之外 AOT 还有以下优点:

  • 在客户端我们不需要导入体积庞大的 angular 编译器,这样可以减少我们 JS 脚本库的大小
  • 使用 AOT 编译后的应用,不再包含任何 HTML 片段,取而代之的是编译生成的 TypeScript 代码,这样的话 TypeScript 编译器就能提前发现错误。总而言之,采用 AOT 编译模式,我们的模板是类型安全的。

2.2 现代编译器工作流程

摘抄维基百科中对 编译器工作流程介绍:

一个现代编译器的主要工作流程如下: 源代码(source code)→ 预处理器(preprocessor)→ 编译器(compiler)→ 汇编程序(assembler)→ 目标代码(object code)→ 链接器(linker)→ 可执行文件(executables),最后打包好的文件就可以给电脑去判读运行了。

网络异常,图片无法展示
|

这里更强调了编译器的作用:将原始程序作为输入,翻译产生目标语言的等价程序

网络异常,图片无法展示
|

目前绝大多数现代编译器工作流程基本类似,包括三个核心阶段:

  1. 解析(Parsing :通过词法分析和语法分析,将原始代码字符串解析成抽象语法树(Abstract Syntax Tree)
  2. 转换(Transformation:对抽象语法树进行转换处理操作;
  3. 生成代码(Code Generation:将转换之后的 AST 对象生成目标语言代码字符串。


三、编译器实现

本文将通过 The Super Tiny Compiler 源码解读,学习如何实现一个轻量编译器,最终实现将下面原始代码字符串(Lisp 风格的函数调用)编译成 JavaScript 可执行的代码

Lisp 风格(编译前) JavaScript 风格(编译后)
2 + 2 (add 2 2) add(2, 2)
4 - 2 (subtract 4 2) subtract(4, 2)
2 + (4 - 2) (add 2 (subtract 4 2)) add(2, subtract(4, 2))

话说 The Super Tiny Compiler 号称可能是有史以来最小的编译器,并且其作者 James Kyle 也是 Babel 活跃维护者之一。

网络异常,图片无法展示
|

让我们开始吧~

3.1 The Super Tiny Compiler 工作流程

现在对照前面编译器的三个核心阶段,了解下 The Super Tiny Compiler  编译器核心工作流程:

网络异常,图片无法展示
|

图中详细流程如下:

  1. 执行入口函数,输入原始代码字符串作为参数;
// 原始代码字符串
(add 2 (subtract 4 2))
  1. 进入解析阶段(Parsing),原始代码字符串通过词法分析器(Tokenizer)转换为词法单元数组,然后再通过 语法分析器(Parser)词法单元数组转换为抽象语法树(Abstract Syntax Tree 简称 AST),并返回;

网络异常,图片无法展示
|

网络异常,图片无法展示
|

  1. 进入转换阶段(Transformation),将上一步生成的 AST 对象 导入转换器(Transformer),通过转换器中的遍历器(Traverser),将代码转换为我们所需的新的 AST 对象

网络异常,图片无法展示
|

  1. 进入代码生成阶段(Code Generation),将上一步返回的新 AST 对象通过代码生成器(CodeGenerator),转换成 JavaScript Code

网络异常,图片无法展示
|

  1. 代码编译结束,返回 JavaScript Code

网络异常,图片无法展示
|


上述流程看完后可能一脸懵逼,不过没事,请保持头脑清醒,先有个整个流程的印象,接下来我们开始阅读代码:

3.2 入口方法

首先定义一个入口方法 compiler ,接收原始代码字符串作为参数,返回最终 JavaScript Code:

// 编译器入口方法 参数:原始代码字符串 input
function compiler(input) {
  let tokens = tokenizer(input);
  let ast    = parser(tokens);
  let newAst = transformer(ast);
  let output = codeGenerator(newAst);
  return output;
}

3.3 解析阶段

在解析阶段中,我们定义词法分析器方法tokenizer  和语法分析器方法parser 然后分别实现:

// 词法分析器 参数:原始代码字符串 input
function tokenizer(input) {};
// 语法分析器 参数:词法单元数组tokens
function parser(tokens) {};


目录
相关文章
|
24天前
|
JSON JavaScript 前端开发
JavaScript原生代码处理JSON的一些高频次方法合集
JavaScript原生代码处理JSON的一些高频次方法合集
|
2天前
|
JavaScript 前端开发 开发工具
【JavaScript 与 TypeScript 技术专栏】TypeScript 如何提升 JavaScript 代码的可读性与可维护性
【4月更文挑战第30天】TypeScript 提升 JavaScript 代码的可读性和可维护性,主要通过静态类型系统、增强代码组织、智能提示与错误检测、文档化和在大型项目中的优势。静态类型减少误解,类和接口提供结构,智能提示提高编码效率,类型注解充当内置文档。在大型项目中,TypeScript 降低理解差异,平滑迁移现有 JavaScript 项目,助力提高开发效率和项目质量。
|
9天前
|
JavaScript 前端开发 算法
< JavaScript小技巧:如何优雅的用【一行代码 】实现Js中的常用功能 >
在开发中,采用简洁的语法和结构,遵循一致的命名规范,具有良好的代码组织和注释,能很好的提高代码的质量。可读性:易于阅读和理解。清晰的命名、简洁的语法和良好的代码结构可以使代码的意图更加明确,降低理解代码的难度,提高代码的可读性。可维护性:易于维护。当代码逻辑清晰、结构简洁时,开发者可以更快速地定位和修复bug,进行功能扩展或修改。同时,可读性高的代码也有助于后续的代码重构和优化。可扩展性:更具有扩展性和灵活性。清晰的代码结构和简洁的代码风格使得添加新功能、修改现有功能或扩展代码更加容易。
< JavaScript小技巧:如何优雅的用【一行代码 】实现Js中的常用功能 >
|
9天前
|
前端开发 JavaScript 容器
JavaScript、CSS像素动画特效代码
此示例创建一个带有像素粒子的容器,每隔300毫秒就会动态添加一个新的像素粒子,然后通过CSS的关键帧动画(`@keyframes`)使它们产生上升和逐渐消失的动画效果。你可以根据需要修改像素粒子的颜色、大小、动画效果和创建速度。
11 0
|
16天前
|
JavaScript
js校验统一社会信用代码
js校验统一社会信用代码
19 0
|
19天前
|
监控 前端开发 JavaScript
如何在浏览器中使用javaScript进行代码调试
【4月更文挑战第11天】在浏览器中调试JavaScript是前端开发的关键技能。使用开发者工具(可通过F12、右键检查或菜单栏访问),遵循以下步骤:1) 打开Sources标签页查看所有脚本;2) 设置断点在需要暂停的代码行;3) 刷新页面触发断点;4) 利用调试工具如Scopes、Watch、Call Stack等检查代码状态;5) 使用Console辅助调试;6) 利用其他工具如Network、Performance和Memory进行性能分析。确保使用最新工具,保持代码清晰,以提升调试效率。
40 4
|
25天前
|
小程序 开发者
微信小程序“Error: xxx.js 已被代码依赖分析忽略,无法被其他模块引用”报错?
微信小程序“Error: xxx.js 已被代码依赖分析忽略,无法被其他模块引用”报错?
|
1月前
|
前端开发 JavaScript UED
【前端】javascript+html+css 家具销售网站(代码+报告)
【前端】javascript+html+css 家具销售网站(代码+报告)
|
2月前
|
JSON 前端开发 JavaScript
16个重要的JavaScript代码
16个重要的JavaScript代码
32 1
|
2月前
|
JavaScript
当当网新用户注册界面——JS代码
当当网新用户注册界面——JS代码
7 0