用 js 写一个 js 解释器过程详解


Posted in Javascript onAugust 02, 2019

用 js 来 编译 js 看起来是个高大上的东西,实际原理其实很简单,无非就是利用 js 对象属性可以用字符串表示 这个特性来实现的黑魔法罢了。

之所以看起来那么 深奥, 大概是由于网上现有的教程,都是动不动就先来个 babylon / @babel/parser 先让大家看个一大串的 AST, 然后再贴出一大串的代码,

直接递归 AST 处理所有类型的节点. 最后成功的把我这样的新手就被吓跑了。

那么今天我写这篇的目的,就是给大家一个浅显易懂,连刚学 js 的人都能看懂的 js2js 教程。

先来看一下效果

用 js 写一个 js 解释器过程详解

一个最简单的解释器

上面有提到,js 有个特性是 对象属性可以用字符串表示,如 console.log 等价于 console['log'], 辣么根据这个特性,我们可以写出一个兼容性极差,极其简陋的雏形

function callFunction(fun, arg) {
 this[fun](arg);
 }
 callFunction('alert', 'hello world');
 // 如果你是在浏览器环境的话,应该会弹出一个弹窗

既然是简易版的,肯定是问题一大堆,js 里面得语法不仅仅是函数调用,我们看看赋值是如何用黑魔法实现的

function declareVarible(key, value) {
 this[key] = value;
 }
 declareVarible.call(window, 'foo', 'bar');
 // window.foo = 'bar'

Tips: const 可以利用 Object.defineProperty 实现;

如果上面的代码能看懂,说明你已经懂得了 js 解释器 的基本原理了,看不懂那只好怪我咯。

稍微加强一下

可以看出,上面为了方便, 我们把函数调用写成了 callFunction('alert', 'hello world'); 但是着看起来一点都不像是 js 解释器,
我们心里想要的解释器至少应该是长这样的 parse('alert("hello world")''), 那么我们来稍微改造一下, 在这里我们要引入 babel 了,

不过先不用担心, 我们解析出来的语法树(AST)也是很简单的。

import babelParser from '@babel/parser';
const code = 'alert("hello world!")';
const ast = babelParser.parse(code);

以上代码, 解析出如下内容

{
 "type": "Program",
 "start": 0,
 "end": 21,
 "body": [
 {
 "type": "ExpressionStatement",
 "start": 0,
 "end": 21,
 "expression": {
 "type": "CallExpression",
 "start": 0,
 "end": 21,
 "callee": {
 "type": "Identifier",
 "start": 0,
 "end": 5,
 "name": "alert"
 },
 "arguments": [
 {
 "type": "Literal",
 "start": 6,
 "end": 20,
 "value": "hello world!",
 "raw": "\"hello world!\""
 }
 ]
 }
 }
 ],
 "sourceType": "module"
}

上面的内容看起来很多,但是我们实际有用到到其实只是很小的一部分, 来稍微简化一下, 把暂时用不到的字段先去掉

{
 "type": "Program",
 "body": [
 {
 "type": "ExpressionStatement",
 "expression": {
 "type": "CallExpression",
 "callee": {
 "type": "Identifier",
 "name": "alert"
 },
 "arguments": [
 {
 "type": "Literal",
 "value": "hello world!",
 }
 ]
 }
 }
 ],
}

我们先大概浏览一遍 AST 里面的所有属性名为 type 的数据

  • ExpressionStatement
  • CallExpression
  • Identifier
  • Literal

一共有 4 种类型, 那么接下来我们把这 4 种节点分别解析, 从最简单的开始

Literal

{
 "type": "Literal",
 "value": "hello world!",
}

针对 Literal 的内容, 我们需要的只有一个 value 属性, 直接返回即可.

if(node.type === 'Literal') {
 return node.value;
}

是不是很简单?

Identifier

{
 "type": "Identifier",
 "name": "alert"
},

Identifier 同样也很简单, 它代表的就是我们已经存在的一个变量, 变量名是node.name, 既然是已经存在的变量, 那么它的值是什么呢?

if(node.type === 'Identifier') {
 return {
 name: node.name,
 value:this[node.name]
 };
}

上面的 alert 我们从 node.name 里面拿到的是一个字符, 通过 this['xxxxx'] 可以访问到当前作用域(这里是 window)里面的这个标识符(Identifier)

ExpressionStatement

{
 "type": "ExpressionStatement",
 "expression": {...}
}

这个其实也是超简单, 没有什么实质性的内容, 真正的内容都在 expression 属性里,所以可以直接返回 expression 的内容

if(node.type === 'ExpressionStatement') {
 return parseAstNode(node.expression);
}

CallExpression

CallExpression 按字面的意思理解就是 函数调用表达式,这个稍微麻烦一点点

{
 "type": "CallExpression",
 "callee": {...},
 "arguments": [...]
}

CallExpression 里面的有 2 个我们需要的字段:

callee 是 函数的引用, 里面的内容是一个 Identifier, 可以用上面的方法处理.

arguments 里面的内容是调用时传的参数数组, 我们目前需要处理的是一个 Literal, 同样上面已经有处理方法了.

说到这里,相信你已经知道怎么做了

if(node.type === 'CallExpression') {
 // 函数
 const callee = 调用 Identifier 处理器
 // 参数
 const args = node.arguments.map(arg => {
 return 调用 Literal 处理器
 });
 callee(...args);
}

代码

这里有一份简单的实现, 可以跑通上面的流程, 但也仅仅可以跑通上面而已, 其他的特性都还没实现。

https://github.com/noahlam/practice-truth/tree/master/js2js

其他实现方式

除了上面我介绍得这种最繁琐得方式外,其实 js 还有好几种可以直接执行字符串代码得方式

1.插入 script DOM

const script = document.createElement("script");
 script.innerText = 'alert("hello world!")';
 document.body.appendChild(script);

2.eval

eval('alert("hello world!")')

3.new Function

new Function('alert("hello world")')();

4.setTimeout 家族

setTimeout('console.log("hello world")');

不过这些在小程序里面都被无情得封杀了...

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JQuery 入门实例1
Jun 25 Javascript
检测input每次的输入是否合法遇到汉字输入就有问题
May 23 Javascript
checkbox使用示例
Aug 23 Javascript
JS获取客户端IP地址、MAC和主机名的7个方法汇总
Jul 21 Javascript
Seajs是什么及sea.js 由来,特点以及优势
Oct 13 Javascript
基于Vue实例对象的数据选项
Aug 09 Javascript
vue Element-ui input 远程搜索与修改建议显示模版的示例代码
Oct 19 Javascript
react实现一个优雅的图片占位模块组件详解
Oct 30 Javascript
深入浅析Vue.js中 computed和methods不同机制
Mar 22 Javascript
JQuery的加载和选择器用法简单示例
May 13 jQuery
微信小程序左右滚动公告栏效果代码实例
Sep 16 Javascript
Node.js API详解之 module模块用法实例分析
May 13 Javascript
vue实现登录页面的验证码以及验证过程解析(面向新手)
Aug 02 #Javascript
详解element-ui中el-select的默认选择项问题
Aug 02 #Javascript
jQuery实现checkbox全选、反选及删除等操作的方法详解
Aug 02 #jQuery
150行Node.js实现的dns代理工具
Aug 02 #Javascript
el-select 下拉框多选实现全选的实现
Aug 02 #Javascript
js动态获取时间的方法分析
Aug 02 #Javascript
微信小程序实现语音识别转文字功能及遇到的坑
Aug 02 #Javascript
You might like
深入PHP操作MongoDB的技术总结
2013/06/02 PHP
PHP实现权限管理功能示例
2017/09/22 PHP
Javascript string 扩展库代码
2010/04/09 Javascript
jquery鼠标放上去显示悬浮层即弹出定位的div层
2014/04/25 Javascript
页面刷新时记住滚动条的位置jquery代码
2014/06/17 Javascript
JS实现可关闭的对联广告效果代码
2015/09/14 Javascript
js获取及判断键盘按键的方法
2015/12/01 Javascript
JavaScript中如何使用cookie实现记住密码功能及cookie相关函数介绍
2016/11/10 Javascript
jQuery向webApi提交post json数据
2017/01/16 Javascript
Angular CLI 安装和使用教程
2017/09/13 Javascript
浅谈Vuex注入Vue生命周期的过程
2019/05/20 Javascript
JS实现多选框的操作
2020/06/24 Javascript
详解vue3.0 diff算法的使用(超详细)
2020/07/01 Javascript
[02:32]“虐狗”镜头慎点 2016国际邀请赛中国区预选赛现场玩家采访
2016/06/28 DOTA
Python中使用OpenCV库来进行简单的气象学遥感影像计算
2016/02/19 Python
Python 爬虫图片简单实现
2017/06/01 Python
Python网络编程之TCP与UDP协议套接字用法示例
2018/02/02 Python
详解Python核心对象类型字符串
2018/02/11 Python
对pandas里的loc并列条件索引的实例讲解
2018/11/15 Python
处理Selenium3+python3定位鼠标悬停才显示的元素
2019/07/31 Python
pycharm不能运行.py文件的解决方法
2020/02/12 Python
15行Python代码实现免费发送手机短信推送消息功能
2020/02/27 Python
Django框架获取form表单数据方式总结
2020/04/22 Python
纯css3制作煽动翅膀的蝴蝶的示例
2018/04/23 HTML / CSS
洲际酒店集团英国官网:IHG英国
2019/07/10 全球购物
PHP中如何创建和修改数组
2012/05/02 面试题
移动通信专业自荐信范文
2013/11/12 职场文书
《散步》教学反思
2014/03/02 职场文书
2014年教师节座谈会发言稿
2014/09/10 职场文书
2014年世界艾滋病日宣传活动总结
2014/11/18 职场文书
2014年精神文明工作总结
2014/12/23 职场文书
中学音乐课教学反思
2016/02/18 职场文书
话题作文之自信作文
2019/11/15 职场文书
pygame面向对象的飞行小鸟实现(Flappy bird)
2021/04/01 Python
SQL优化老出错,那是你没弄明白MySQL解释计划用法
2021/11/27 MySQL
win10识别不了U盘怎么办 win10系统读取U盘失败的解决办法
2022/08/05 数码科技