带你揭开神秘的javascript AST面纱之AST 基础与功能
作者:京东科技 周明亮
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 */ const a = 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)) 写入。
现在,我们就可以掌握新的方法,进行代码改造。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
从0到1构建基于自身业务的前端工具库
作者:京东零售 吴迪 前言 在实际项目开发中无论 M 端、PC 端,或多或少都有一个 utils 文件目录去管理项目中用到的一些常用的工具方法,比如:时间处理、价格处理、解析url参数、加载脚本等,其中很多是重复、基础、或基于某种业务场景的工具,存在项目间冗余的痛点以及工具方法规范不统一的问题。 在实际开发过程中,经常使用一些开源工具库,如 lodash,以方便、快捷的进行项目开发。但是当 npm上没有自己中意或符合自身业务的工具时,我们不得不自己动手,此时拥有自己的、基于业务的工具库就显得尤为重要。 我们所熟知的Vue、React等诸多知名前端框架,或公司提供的一些类库,它们是如何开发、构建、打包出来的,本文将带领你了解到如何从0到1构建基于自身业务的前端工具库。 构建工具库主流方案 1. WEBPACK webpack 提供了构建和打包不同模块化规则的库,只是需要自己去搭建开发底层架构。 vue-cli,基于 webpack , vue-cli 脚手架工具可以快速初始化一个 vue 应用,它也可以初始化一个构建库。 2. ROLLUP rollup 是一个专门针对JavaScr...
- 下一篇
一个前端大佬的十年回顾 | 漫画前端的前世今生
作者:京东科技 胡骏 引言 岁月如梭,十载流年 前端技术,蓬勃向前 HTML,CSS,JavaScript 演绎出璀璨夺目的技术画卷 回到十年前,前端技术就像一名戴着厚重眼镜的书呆子,总是小心翼翼,被各种各样的浏览器兼容性问题欺负(就像在小学被欺负一样)。 但随着时间的推移,这个书呆子开始锻炼,变得越来越强壮,终于能够对抗那些讨厌的兼容性问题 进入中学时期,前端技术遇到了那个改变它一生的朋友——jQuery。在jQuery的帮助下,前端技术变得更加自信,能够在各种浏览器之间轻松穿梭(就像找到了武林秘籍,功力大增)。 随后,前端技术开始追求更高的境界。它遇到了三位美丽的姑娘:Angular、React和Vue。这三位姑娘带给了前端技术无尽的魅力,让它迅速崛起,成为了技术江湖中的一股新兴力量。 如今,前端技术已经变得越来越强大,像一个熟练掌握各种武功的高手。它的发展速度之快,令人瞠目结舌,仿佛在短短十年内成为了武林盟主。它带领着一群忠诚的拜金党(程序员),在技术江湖中闯荡,创造了一个又一个的传奇。 而现在,前端技术正在为未来的挑战做准备,它还能带给我们多少惊喜,以及如何抵抗那些不断涌现的挑...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- Docker安装Oracle12C,快速搭建Oracle学习环境
- CentOS7安装Docker,走上虚拟化容器引擎之路
- Linux系统CentOS6、CentOS7手动修改IP地址
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS关闭SELinux安全模块
- CentOS7编译安装Gcc9.2.0,解决mysql等软件编译问题
- Hadoop3单机部署,实现最简伪集群
- CentOS6,7,8上安装Nginx,支持https2.0的开启