代码 parse 成 AST 之后,可以对 AST 进行 transform,然后 generate 成目标代码,这是转译器(transpiler)的流程,也可以对 AST 进行解释执行,这是解释器(interpreter)的流程。这一节,我们来基于 babel parser 来实现一个简单的 js 解释器。
# v8 的编译流水线
v8 包括 4 部分,parser、ignation 解释器,JIT 编译器,还有 garbage collector(垃圾回收器)。
- parser 负责把源码 parse 成 AST。
- ignation 解释器负责把 AST 转成字节码,然后解释执行
- turbofan 可以把代码编译成机器码,直接执行
- gc 负责堆内存的垃圾回收

其实最早期的 v8 是没有字节码的,就是直接解释执行 AST:

这种直接解释执行 AST 的解释器叫做 tree walker 解释器,这一节,我们来实现一下这种 js 解释器。
# 实现 JS 解释器
# 思路分析
当 parser 把 源码 parse 成 AST 之后,其实已经能够拿到源码的各部分信息了,比如
const a = 1 + 2;
@前端进阶之旅: 代码已经复制到剪贴板
对应的 AST 是这样的

当我们处理到 BinarayExpression 节点,operator 是 +,会做加法运算,取左右两边的值相加。
当我们处理到 NumercLiteral 节点,是数字字面量,直接返回它的值(value)。
当我们处理到 Identifier 节点,是标识符,直接返回名字(name)。
当我们处理到 VariableDeclarator,我们就知道是一个变量声明语句,要在作用域 (scope)中放一个属性,属性名为 id 的值, 属性值为 init 的值。而 id 和 init 可以求出来。
就这样,我们就完成了这段代码的解释执行。
# 代码实现
先搭一个基本的结构:
const parser = require('@babel/parser');
const { codeFrameColumns } = require('@babel/code-frame');
const sourceCode = `
const a = 1 + 2;
`;
const ast = parser.parse(sourceCode, {
sourceType: 'unambiguous'
});
const evaluator = (function() {
const astInterpreters = {
Program (node, scope) {
node.body.forEach(item => {
evaluate(item, scope);
})
}
}
const evaluate = (node, scope) => {
try {
return astInterpreters[node.type](node, scope);
} catch(e) {
if (e && e.message && e.message.indexOf('astInterpreters[node.type] is not a function') != -1) {
console.error('unsupported ast type: ' + node.type);
console.error(codeFrameColumns(sourceCode, node.loc, {
highlightCode: true
}));
} else {
