作者:京东科技 周明亮

AST 基础与功能

在前端里面有一个很重要的概念,也是最原子化的内容,就是 AST ,几乎所有的框架,都是基于 AST 进行改造运行,比如:React / Vue /Taro 等等。 多端的运行使用,都离不开 AST 这个概念。

在大家理解相关原理和背景后,我们可以通过手写简单的编译器,简单实现一个 Javascript 的代码编译器,编译后在浏览器端正常运行。

创建数字小明,等于六加一。
创建数字小亮,等于七减二。
输出,小明乘小亮。

通过实现一个自定义的编译器,我们发现我们自己也能写出很多新的框架。最终目标都是通过编译转换,翻译为浏览器识别的 Javascript + CSS + HTML。

没错!翻译翻译~

当然我们也可以以这个为基础,去实现跨端的框架,直接翻译为机器码,跑到各种硬件上。当然一个人肯定比较困难,你会遇到各种各样的问题需要解决,不过没关系,只要你有好的想法,拉上一群人,你就能实现。

大家记得点赞,评论,收藏,一键三连啊~

分析器

说到这个代码语义化操作前,我们先说说分析器,其实就是编译原理。当你写了一段代码,要想让机器知道,你写了啥。

那机器肯定是要开始扫描,扫描每一个关键词,每一个符号,我们将进行词法分析的程序或者函数叫作词法分析器(Lexical analyzer),通过它的扫描可以将字符序列转换为单词(Token)序列的过程。

扫描到了关键词,我们怎么才能把它按照规则,转换为机器认识的特定规则呢?比如你扫描到:

const a = 1

机器怎么知道要创建一个 变量a并且等于1呢?

所以,这时候就引入一个概念:语法分析器(Syntactic analysis,Parser)。通过语法分析器,不断的调用词法分析器,进行语法检查、并构建由输入的单词组成的数据结构(一般是语法分析树、抽象语法树等层次化的数据结构)。

在JS的世界里,这个扫描后得到的数据结构抽象语法树 【AST】。可能很多人听过这个概念,但是具体没有深入了解。机缘巧合,刚好我需要用到这个玩意,今天就简单聊聊。

抽象语法树 AST

AST是Abstract Syntax Tree的缩写,也就是:抽象语法树。在代码的世界里,它叫这个。在语言的世界里面,他叫语法分析树。

语言世界,举个栗子:

我写文章。

语法分析树:

主语:我,人称代词。

谓语:写,动词。

宾语:文章,名词。

长一点的可能会有:主谓宾定状补。是不是发现好熟悉,想当年大家学语文和英语,那是一定要进行语法分析,方便你理解句子要表达的含义。

PS:对我来说,语法老难了!!!哈哈哈,大家是不是找到感觉了~

接下来我们讲讲代码里面的抽象语法树。

const me = "我"
function write() {
console.log("文章")
}

那我们用来进行语法分析,能够得到什么内容了?这时候我们可以借助已有的工具,将他们进行分析,进行一个初级入门。

其实我们也可以完全自己进行分析,不过这样就不容易入门,定义的语法规则很多,如果只是看,很容易就被劝退了。而通过辅助工具,我们可以很快接受相关的概念。

常用的工具有很多,比如:Recast 、Babel、Acorn 等等

也可以使用在线 AST 解析:AST Explorer,左上角菜单可以切换到各种解析工具,并且支持各类编程语言的解析,强大好用,可以用来学习,帮助你理解 AST。

为了帮助大家理解,我们一点点的进行解析,并且去掉了部分属性,留下主干部分,完整的可以通过在线工具查看。【不同解析器,对于根节点或者部分属性稍有区别,但是本质是一样的。

{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "me"
},
"init": {
"type": "Literal",
"value": "我",
"raw": "\"我\""
}
}
],
"kind": "const"
},
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "write"
},
"params": [],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "console"
},
"property": {
"type": "Identifier",
"name": "log"
}
},
"arguments": [
{
"type": "Literal",
"value": "文章",
"raw": "\"文章\""
}
]
}
}
]
}
}
],
"sourceType": "module"
}

接下来,我们一个一个节点看,首先是第一个节点Program

{
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"kind": "const"
...
},
{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "write"
},
....
}
],
"sourceType": "module"
}

Program是代码程序的根节点,通过它进行节点一层一层的遍历操作。 上面我们看出它有两个节点,一个是变量声明节点,另外一个是函数声明节点。

如果我们再定义一个变量或者函数,这时候 body 就又会产生一个节点。我们要扫描代码文件时,我们就是基于 body 进行层层的节点扫描,直到把所有的节点扫描完成。

    {
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "me"
},
"init": {
"type": "Literal",
"value": "我",
"raw": "\"我\""
}
}
],
"kind": "const"
},

上面对应的代码,就是const me = "我",这个节点告诉我们。 声明一个变量,使用类型是:VariableDeclaration, 他的唯一标识名是:me,初始化值:"我"。

后续的函数分析,也是一样的。

{
"type": "FunctionDeclaration",
"id": {
"type": "Identifier",
"name": "write"
},
"params": [],
"body": {
"type": "BlockStatement",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "CallExpression",
"callee": {
"type": "MemberExpression",
"object": {
"type": "Identifier",
"name": "console"
},
"property": {
"type": "Identifier",
"name": "log"
},
},
"arguments": [
{
"type": "Literal",
"value": "文章",
"raw": "\"文章\""
}
],
}
}
]
}
}

这个节点,清楚的告诉我们,这个函数名是什么,他里面有哪些内容,入参是什么,调用了什么函数对象。

我们发现,通过语法分析器的解析,我们可以把代码,变成一个对象。这个对象将代码分割为原子化的内容,很容易能够帮助机器或者我们去理解它的组成。

这个就是分析器的作用,我们不再是一大段一大段的看代码逻辑,而是一小段一小段的看节点。

有了这个我们可以干什么呢?

AST 在 JS 中的用途

1. 自定义语法分析器,写一个新的框架。

通过对现有的 AST 理解,我们可以依葫芦画瓢,写出自定义的语法分析器,转成自定义的抽象语法树,再进行解析转为浏览器可识别的 Javascript 语言,或者其他硬件上能识别的语言。

比如:React / Vue 等等框架。其实这些框架,就是自定义了一套语法分析器,用他们特定的语言,进行转换,翻译翻译,生成相关的DOM节点,操作函数等等 JS 函数。

2. 利用已有语法分析器,实现多端运行。

通过已有的 AST,我们将代码进行翻译翻译,实现跨平台多端运行。我们将得到代码进行语法解析,通过遍历所有的节点,我们将他们进行改造,使得它能够运行在其他的平台上。

比如:Taro / uni-app 等等框架。我们只要写一次代码,框架通过分析转换,就可以运行到 H5 / 小程序等等相关的客户端。

3. 进行代码改造,预编译增强处理。

依旧是通过已有的 AST,我们将代码进行分析。再进行代码混淆,代码模块化处理,自动进行模块引入,低版本兼容处理。

比如:Webpack / Vite 等等打包工具。我们写完代码,通过他们的处理,进行增强编译,增强代码的健壮性。

AST 的应用实践

我们在进行框架的改造或者适配时,我们可能才会用到这个。常规的方法,可能有两种:

  • 按照特定的写法,通过正则表达式,直接进行大段代码替换。
  • /** mingliang start */consta=1/** mingliang end */

如,我们找到这段代码注释,直接通过code.replace(/mingliang/g, 'xxxx')类似这种方式替换。

  • 通过引入运行,改造相关的变量,再重新写入。
// a.js
cost config = { a: 1 }
return config

我们可能先let config = require(a.js)运行这个文件,我们就得到了这个config这个变量值。

之后我们改写变量config.a = 2,

最后,重新通过fs.writeSync('a.js', 'return ' + JSON.stringify(config, null, 2))写入。

现在,我们就可以掌握新的方法,进行代码改造。

带你揭开神秘的javascript AST面纱之AST 基础与功能的更多相关文章

  1. 云小课|带你揭开IP地址的神秘身份

    摘要:本文带你了解网络云产品和相关的知识内容. 华为云网络服务大家族提供了丰富的云产品,可以满足用户的各种网络互联需求.相应地,华为云帮助中心也贴心的奉上了你想了解的所有网络云产品知识. 可是小课最近 ...

  2. JavaScript 学习笔记: 扩充类型的功能

    JavaScript 是允许给基本类型扩充功能的.例如,可以通过对Object.prototype增加方法,可以让该方法对所有的对象都可用. 这样的方式对函数,数组,字符串,数字,正则表达式和布尔值同 ...

  3. 通过javascript库JQuery实现页面跳转功能代码

    通过javascript库JQuery实现页面跳转功能代码的四段代码实例如下. 实例1: 1 2 3 4 $(function(){ var pn = $("#gotopagenum&quo ...

  4. 使用javascript,jquery实现的图片轮播功能

    使用javascript,jquery实现的图片轮播功能本功能采用最基础的javascript和一些简单的jquery技术实现,易理解,以修改使用,代码简易,适合刚开始接触到网站开发的朋友们参考.可以 ...

  5. JavaScript学习(6)-文档对象模型基础

    JavaScript学习6-文档对象模型基础 1.节点方法 节点对象方法(W3C DOM Level2) 方法 说明 appendChild(newChild) 添加子节点到当前节点的末端 clone ...

  6. Atitit.软件开发的最终的设计 dsl化,ast化(建立ast, 解析执行ast)

    Atitit.软件开发的最终的设计 dsl化,ast化(建立ast, 解析执行ast) 1. 使用js,html 撰写dsl1 1.1. 架构图1 1.2. html2 1.3. Js2 1.4. C ...

  7. JavaScript在HTML中的基础用法总结

    网页主要由三部分组成,分别为html.CSS和Javascript.如果说HTML是肉身,CSS是皮相,那Javascript就是灵魂.因此,三者的联系与融合则至关重要.本文就来为大家讲解一下Java ...

  8. javascript订阅模式浅析和基础实例

    前言 最近在开发redux或者vux的时候,状态管理当中的createStore,以及我们在组件中调用的dispatch传递消息给状态管理中心,去处理一些操作的时候,有些类似我们常见到订阅模式 于是写 ...

  9. 带你揭开ATM的神秘面纱

    相信大家都用过ATM取过money吧,但是有多少人真正是了解ATM的呢?相信除了ATM从业者外了解的人寥寥无几吧,鄙人作为一个从事ATM软件开发的伪专业人士就站在我的角度为大家揭开ATM的神秘面纱吧. ...

  10. 性能再提升70%?大咖前瞻带你揭开.NET6的神秘面纱!

    本月初微软官宣.NET 6 的RC1即将在11月正式发布,这意味着.NET6正式版跟我们见面的时间又近了一步.在之前的.NET6预览版本中,微软加入了大量新功能特性,而在最终版本中将不再额外加入新的内 ...

随机推荐

  1. jmeter设置支持https方法

    2020-2-26,疫情影响下第一天上班,今年想把自己学到的测试方面的知识记录下来,方便自己方便有需要的人,废话不多说,开启第一篇随笔,jmeter设置. 最近在测接口性能,涉及https的接口,不知 ...

  2. elasticsearch的Kibana基本操作

    幂等(idempotent.idempotence)是一个数学与计算机学概念,常见于抽象代数中. 在编程中一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同.幂等函数,或幂等方法,是 ...

  3. 自学JavaDay02_class01

    注释 单行注释: //单行注释 多行注释 /** 多行注释* 多行注释* */ 文档注释 /** * 文档注释 * 文档注释 */ 标识符 关键字 标识符 所有的标识符都应该以字母(A-Z 或者 a- ...

  4. CentOS7 yum方式安装mysql 5.7

    1.检查服务器上有没有安装mysqlyum安装方式:yum list installed mysql*yum卸载 yum remove 已安装的包卸载完安装包后,删除安装文件rm -rf /var/l ...

  5. Spring 笔记二 IOC

    1.IOC IOC:Inversion  od  controller ,控制反转,控制的是资源的获取方式,由原来的自己创建 new 变成 Spring 容器创建. DI:Dependency  In ...

  6. golang sync.Once用法

    sync.Once能确保实例化对象Do方法在多线程环境只运行一次,内部通过互斥锁实现 Do方法 Once类型的Do方法只接收一个参数,参数的类型必须是func(),即无参数无返回的函数.该方法的功能并 ...

  7. golang sync.WaitGroup错误使用导致死锁以及noCopy结构体介绍

    背景 项目中遇到死锁,使用搜索引擎搜索goroutine堆栈中出现的"sync.runtime_Semacquire deadlock"时,搜到一篇说sync.WaitGroup死 ...

  8. C++常见报错信息和原因的对应关系

    1. 无法找到 xxx.dll 没有把动态链接库和exe放在一个文件夹下 2. 不允许使用不完整的类型 指的是忘了加头文件 3. link err .无法解析的外部符号 指的是 lib 库没有配置对 ...

  9. 【node打包缺包】Error: Can't walk dependency graph: Cannot find module 'jquery' from

    问题: 在使用node打包时,终端报错提示Error: Can't walk dependency graph: Cannot find module 'jquery' from/.../ 这个提示缺 ...

  10. 使用 Transformers 在你自己的数据集上训练文本分类模型

    最近实在是有点忙,没啥时间写博客了.趁着周末水一文,把最近用 huggingface transformers 训练文本分类模型时遇到的一个小问题说下. 背景 之前只闻 transformers 超厉 ...