用JS实现一个JS引擎竟如此简单(二)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: 这是很久之前的一个念想,当时为了加深自己对js的理解,明白js引擎是如何工作的。 于是从上网找了一个giao-js,感觉还不错,因此想学习一下。

解释器


解释器,就是遍历AST语法树,然后根据Node节点类型,去执行或计算每个节点。

这里实现一个JS解释器,需要对AST语法树Node节点每个类型做区分判断,主要有以下几种:

  • 变量
  • 作用域以及作用域链
  • 上下文This指向
  • 条件判断
  • For循环,其中的 break和continue
  • 函数部分 Function
  • 生成器 Generator
  • 异步 Async


因此我们需要几个类去保存相关的值:

  • Scope,作用域类,保存作用域内的值以及作用域链(当前作用域可以找到父级作用域链)
  • Visitor,AST树Node节点处理类,里面有函数visitNode(node, scope),用来处理对应node类型,其中VISITOR是所有类型函数的Map对象,用来快速查询
  • Variable,变量存储类,用来存储变量类型和值


参考代码:

/**
 * 遍历AST语法树,并执行对应的处理函数
 * @param {*} node 
 * @param {*} scope 
 */
visitNode(node, scope) {
      const { type } = node;
    if (VISITOR[type]) {
        return VISITOR[type]({ node, scope, context: this });
    }
    return undefined;
}

变量和作用域


在JavaScript中,对变量的声明通常是绑定在作用域中的,而作用域分为以下几种:

  • 全局作用域,全局作用域中仅存在一处,即为最上级的环境。
  • 函数作用域,函数存在并执行时,内部存储函数作用域
  • 块级作用域,每隔block块{}都可产生作用域,如if for while等


我们举个例子,以var a = 1;为例,我们需要需要哪些代码,才能实现从AST树解析,将变量a被声明在全局变量作用域中,具体步骤如下图:

762279b5195b081cf0a7d74c0159c09.png

这里实现一个作用域Scope类,参考代码如下:

class Scope {
  /**
     * 
     * @param {*} type 
     * @param {*} parent 
     */
    constructor(type, parent) {
        this.parent = parent || null; // 父级作用域
        this.type = type; // 作用域类型 Global, Function, Block
        this.targetScope = new Map();  // 当前作用域
    }
    /**
     * 变量声明方法,变量已定义则抛出语法错误异常
     * @param {*} kind 变量类型
     * @param {*} rawName  变量名
     * @param {*} value 变量值
     * @returns 
     */
    declare(kind, rawName, value) {
        this.targetScope.set(rawName, value);
    }
}


上下文This


上下文的this对象其实指的就是当前作用域,然而我们了解过JS中的this是可以改变的,如:

  • call()bind()apply()等函数,当执行到相关函数的时候,需要将传递进来scope的替换成当前的scope
  • ES6中的箭头函数等,this指向上一级

这些都需要在解析代码的时候注意的逻辑问题。

其他类型解释


条件判断


IfStatement,里面有属性: test为判断条件,consequent为条件成立时执行的语句,alternate为条件不成立时执行的语句,参考代码如下:

// visitNode会执行AST语法树节点函数
const { test, consequent, alternate } = node;
const testValue = visitNode(test, scope);
if (testValue) {
    if(consequent){
       visitNode(consequent, scope);
    }
} else {
    if(alternate){
       visitNode(alternate, scope);
    }
}

其他部分逻辑就不会在这里一一描述,具体Node类型都有自己的判断逻辑,因此想要了解完整逻辑,可以到完整源码里查看,注解都十分清晰。

完整源码地址在:github.com/qiubohong/q…

总结


本文涉及的东西有点多,花了好几天时间才弄明白,因此有些知识点在这里做一下小总结:

  • JS引擎是有三部分组成的,分别是:词法分析,语法解析解释器
  • 词法解析和语法解析,最终的目标是生成符合ESTree规范的AST语法树
  • 解释器的作用就是依据AST语法树去执行相关逻辑,输出所需要的最终结果
  • 比较重要的部分在于变量、作用域和作用域链的实现
  • 其他部分则是依据对应ECMAScript 规范实现对应逻辑皆可

ECMAScript 规范


其实,JS解释器实现起来不难,就是需要对JS执行逻辑有完整的认识,不仅仅只是上面几个部分,但是基本上AST语法树都已经包括在里面了,所以这个时候需要你对ECMAScript 规范有一定了解才能完整实现解释器。


所以这里贴一个ECMAScript 规范链接,作为后续完整解释器的扩展。

使用 ECMAScript 指代由 Ecma International Technical Committee 39 负责编撰的 ECMAScript Language Specification,而使用 JavaScript 来指代我们日常使用的那个常见编程语言。

我们可以在 tc39.es/ecma262 获取到最新的 ECMAScript 规范


如何阅读规范呢?

  • 惯例与基础:如在 ECMAScript 中 Number 的定义是什么,亦或者 throw a TypeError exception 语句代表什么含义;
  • 语言语法产生式:如如何写一个符合规范的 for-in 循环;
  • 语言静态语义:如一个 VariableDeclaration 如何确定一个变量声明;
  • 语言运行时语义:如一个 for-in 循环的执行例程的定义;
  • APIs:如 String.prototype.substring 等内置对象的方法例程定义。

能做什么


有了我们自己的JS引擎后我们能做些什么,其实目前市面上已经有很多应用场景,比如:

  • Babel,最常见JavaScript编译器,能够将js代码编译成我们想要的任一版本的ECMAscript标准,基于 acorn.js优化
  • ESlint,最常用的代码质量扫描工具,能找到代码不符合规范的地方,基于Espree.js进行分析
  • pretiier,最常用的代码格式工具,能帮忙把代码格式优化
  • js沙箱安全机制,之前写过一篇文件低代码系列——js沙箱设计里提到过,想要完整动态执行js代码,最好的方式是拥有自己的js解释器

参考资料


目录
相关文章
|
1月前
|
Web App开发 JavaScript 前端开发
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念
Node.js 是一种基于 Chrome V8 引擎的后端开发技术,以其高效、灵活著称。本文将介绍 Node.js 的基础概念,包括事件驱动、单线程模型和模块系统;探讨其安装配置、核心模块使用、实战应用如搭建 Web 服务器、文件操作及实时通信;分析项目结构与开发流程,讨论其优势与挑战,并通过案例展示 Node.js 在实际项目中的应用,旨在帮助开发者更好地掌握这一强大工具。
46 1
|
2月前
|
JavaScript 前端开发 Java
JS引擎V8
【10月更文挑战第9天】
35 0
|
4月前
|
Web App开发 JavaScript 前端开发
什么是JavaScript引擎
【8月更文挑战第14天】什么是JavaScript引擎
107 1
|
6月前
|
XML 缓存 JavaScript
一篇文章讲明白JS模板引擎之JST模板
一篇文章讲明白JS模板引擎之JST模板
61 2
|
7月前
|
JavaScript 前端开发 NoSQL
【MongoDB 专栏】MongoDB 的 JavaScript 引擎与脚本执行
【5月更文挑战第11天】MongoDB 的 JavaScript 引擎允许在服务器端直接执行脚本,提升效率并实现定制化操作。脚本环境提供独立但与数据库关联的运行空间,引擎负责脚本的解析、编译和执行。执行过程包括脚本提交、解析、编译和执行四个步骤。掌握脚本逻辑设计和 JavaScript 语言特性对于高效利用这一功能至关重要。例如,通过脚本可以计算商品总销售额,增强数据库操作的灵活性。
124 1
【MongoDB 专栏】MongoDB 的 JavaScript 引擎与脚本执行
|
6月前
|
缓存 自然语言处理 前端开发
深入剖析JavaScript引擎的工作原理
【6月更文挑战第3天】JavaScript引擎由解析器、解释器、优化器和垃圾回收器组成,它们协同完成代码的解析、编译和执行。解析器将源代码转为抽象语法树(AST),编译阶段进行作用域分析和变量提升。解释器执行AST,优化器在代码频繁执行时进行即时编译以提高性能。垃圾回收器自动回收不再使用的内存,防止泄漏。理解这些原理有助于优化代码和提升Web应用性能。
60 1
|
7月前
|
JavaScript 前端开发 Go
8 大博客引擎 jekyll/hugo/Hexo/Pelican/Gatsby/VuePress/Nuxt.js/Middleman 对比
探索各类博客引擎:Jekyll、Hugo、Hexo、Pelican、Gatsby、VuePress、Nuxt.js和Middleman的对比,包括语言、模板引擎、速度、社区活跃度等。了解每种引擎的优缺点,助你选择合适的博客构建工具。查看详细文章以获取更多实战和安装指南。
|
7月前
|
JavaScript 前端开发 开发者
Vue.js深度解析:前端开发的生产力引擎
Vue.js深度解析:前端开发的生产力引擎
115 0
|
JavaScript 前端开发 算法
带你读《现代Javascript高级教程》十一、JavaScript引擎的垃圾回收机制(1)
带你读《现代Javascript高级教程》十一、JavaScript引擎的垃圾回收机制(1)
|
JavaScript 前端开发 算法
带你读《现代Javascript高级教程》十一、JavaScript引擎的垃圾回收机制(2)
带你读《现代Javascript高级教程》十一、JavaScript引擎的垃圾回收机制(2)