本文已参与「新人创作礼」活动,一起开启掘金创作之路。
前言
最近在看vue3.0的compile模块,用到了AST抽象语法树,在网上找资料发现国内比较缺乏相关内容,看到热门的是用一个插件去解析和操纵JavaScript版的AST语法树,但我想了解的是单页面应用里的标签是怎么形成语法树的,自己能否学着写一下。
一、AST抽象语法树是什么?
AST(Abstract Syntax Tree)是源代码的抽象语法结构树状表现形式
个人理解为将源代码转化为各种关键字再以树的形式呈现
二、看一眼AST语法树
vue3.0模板资源管理器 vue-next-template-explorer.netlify.app/
按F12 打开控制台,我们就能看到vue转换过来的AST语法树
我们了解到语法树长什么样子,我们就能尝试去写属于我们的AST抽象语法树了。
三、写一下AST语法树
// 首先我们编写以字符串形式的标签
const templateStr = `<div id="app">
<h1 @click="add" class="item" :id="name">{{count}}</h1>
<p>摸鱼真快乐</p>
</div>`
// 将标签放入我们的主函数中
let ast = parse(template)
function parse(template) {
//第一步
const tokens = semanticSplit(template)
}
第1步、语义拆分,将标签拆分成数组
function semanticSplit(template) {
// 我们的目标是返回一个数组
// [{ value: 'div', type: 'tagstart' }, { type: props }]
let res = []
let type = ''
let val = ''
function push() {
if(val) {
if(type === 'tagstart') val = val.slice(1) // 过滤 <
if(type === 'tagend') val = val.slice(2) // 过滤 />
tokens.push({
type, val
})
val = ''
}
}
for(let i = 0; i <= template.length; i++){
let ch = template[i]
if(ch === '<') {
push() // 以 < 开头的字符,语义转换,将之前收集的val push到 res 中
if(template[i+1] === '/') {
type = 'tagend' // </ 结束标签
} else {
type = 'tagstrat' // <div 开始标签
}
}
if(ch === '>') {
// 标签结束
push()
type = 'text'
continue
} else if(/[\s]/.test(ch)) {
push()
type = 'props'
continue
}
val += ch
}
return res
}
这样我们就完成第一步从标签转换成数组。
第2步、数组转换成树,利用递归实现AST
function parse(template) {
//第一步
const tokens = semanticSplit(template)
// 数组 => 树
let cur = 0 // 数组下标从0开始计数
// 树的根节点
let ast = {
type: 'root',
props: [],
children: [] // 子元素
}
while(cur < tokens.length) {
ast.children.push(walk())
}
return ast
// 递归函数
function walk() {
let token = tokens[cur]
if(token.type === 'tagstart') {
let node = { // 开始标签 新建一个node
type: 'element',
tag: token.val,
props: [],
children: []
}
token = tokens[++cur]
// 往下走 一直走到 tagend or tagstart 之中,都是当前的标签的内容
while(token.type !== 'tagend') {
if(token.type === 'props') {
node.props.push(walk())
} else {
node.children.push(walk())
}
}
cur ++
return node
}
if(token.type === 'tagend') {
cur++
}
if(token.type === 'text') {
cur++
return token
}
if(token.type === 'props') {
cur++
// id = name
const [key,val] = token.val.split('=')
return { key, val }
}
}
}
这就是我们生成的AST抽象语法树了,由于是编写语法树的学习,功能和边界问题没有过多得考虑,如果想更深入了解可以下载
Vue3源码github.com/vuejs/vue-n…
目录为 packages > compiler-core > src > parse.ts
在parseChildren函数中有更完整的写法
也可以看the-super-tiny-compiler的源码github.com/jamiebuilds… 在parse函数中也有类似的写法
相信到这对AST抽象语法树有一定的了解了,不知道你对抽象语法树的理解是怎么样的呢?