随笔 - 253,  文章 - 0,  评论 - 8,  阅读 - 24万

Vue 【进阶】- AST 抽象语法树

1. AST 简介

在开发Vue的时候编译器会将模板语法编译成正常的HTML语法,而直接编译的时候是非常困难的,因此此时会借助AST抽象语法树进行周转,进而变为正常的HTML语法,使编译工作变得更加简单。

抽象语法树的本质上是一个JS对象,Vue在审视所有HTML结构时是以字符串的新式进行的,最终将其解析为JS对象。AST抽象语法树服务于模板编译,将一种语法翻译为另一种语法。在Vue中将模板语法编译为HTML语法,自己作为中转站。

2. 抽象语法树和虚拟节点的关系

AST 只会在 编译阶段 出现,用来描述 template 模板
虚拟DOM 只在运行时出现,用来描述 dom, render 的时候产生新的 虚拟dom

首次渲染是很消耗性能的,所以,用运行时版本,使用webpack -> vue-loader 可以将我们的 vue 文件在打包编译时就转成了一个个 render 函数

为什么 AST 不可以直接当 virtual DOM 呢?为什么我们要写 render 函数,而不是直接写虚拟 DOM?
vue 的响应式机制是数据改变时重新执行 render 函数,生成新的虚拟 dom,然后 diff 比对、渲染。如果直接编译成虚拟 dom,数据改变时如何更新虚拟 dom?AST 重新生成虚拟 dom?做不到呀,因为代码没变。

其实 AST 和 virtual DOM 的样子都差不多

AST的样子

虚拟dom的样子

3. 手撸解析为 AST 的过程

[https://www.cnblogs.com/caijinghong/p/16918948.html]建议先看这篇栈相关处理得算法

index.js

import parse from './parse'
var template = `
<div>
<h3>你好</h3>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
</div>
`
console.log(parse(template))

parse.js

// parse, 主函数
export default function(template) {
template = template.replace(/\s*/g,'')
// console.log(template)
// 指针
let index = 0,
// 剩余
tail = template,
// 栈
stack = [],
// 匹配头标签<div>
RegExp1 = /^\<(\w+)\>/,
// 匹配尾部 content</div>和前面内容
RegExp2 = /(.*?)(\<\/\w+\>)/;
while(index < template.length) {
if(RegExp1.test(tail)) {
stack.push({
tag: RegExp.$1,
type: 1,
children: []
})
index += RegExp.$1.length + 2
tail = template.substring(index)
} else if(RegExp2.test(tail) && !RegExp1.test(tail)){
let endstack = stack[stack.length - 1]
if(RegExp.$1) {
endstack.children.push({
text: RegExp.$1,
type: 3
})
}
let child = stack.pop()
// 最后的返回值
if(!stack.length) return child
stack[stack.length - 1].children.push(child)
index += (RegExp.$1.length + RegExp.$2.length)
tail = template.substring(index)
}
}
}

最后得 AST 树

加入属性版

var template = `
<div>
<h3 class="test" name="666" style="color:red">你好</h3>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
</div>
`

parse.js

import handleAttr from './handleAttr'
// parse, 主函数
export default function(template) {
// 去掉首尾
template = template.replace(/(^\s*|\s*$)/g,'')
// 指针
let index = 0,
// 剩余
tail = template,
// 栈
stack = [],
// 匹配头标签<div>,还能匹配属性props
RegExp1 = /^\<(\w+)(\s*.*?)\>/,
// 匹配尾部 content</div>和前面内容
RegExp2 = /(.*?)(\<\/\w+\>)/;
while(index < template.length) {
if(RegExp1.test(tail)) {
let obj = { tag: RegExp.$1, type: 1, children: [] }
// 有 attrs
if(RegExp.$2) {
obj.attrs = [handleAttr(RegExp.$2)]
}
stack.push(obj)
// 加上'<>'的长度
index += (RegExp.$1.length + 2 + RegExp.$2.length)
// 并且清掉剩下的头部的空格
tail = template.substring(index).replace(/(^\s*)/g,'')
// 加上空格的长度
index += RegExp.$1.length
} else if(RegExp2.test(tail) && !RegExp1.test(tail)){
let endstack = stack[stack.length - 1]
if(RegExp.$1) {
endstack.children.push({
text: RegExp.$1,
type: 3
})
}
let child = stack.pop()
// 最后的返回值
if(!stack.length) return child
stack[stack.length - 1].children.push(child)
index += (RegExp.$1.length + RegExp.$2.length)
tail = template.substring(index).replace(/(^\s*)/g,'')
index += RegExp.$1.length
}
}
}

handleAttr.js 处理属性对象

export default function(attrs) {
let obj = {}
let attrsToArr = attrs.split(' ')
attrsToArr
.filter(item => item)
.forEach(item => {
let arrData =item.split('=')
obj[arrData[0]] = arrData[1]
})
return obj
}

最后处理得如下

属性的写法改进

原来的写法 split 通过空格来处理得数组有个缺陷, <h3 class="test name" name="666" style="color:red">你好</h3>,这样处理得数组就不对,所以进行改进

方法一(指针法)

一个变量作为保存结果的作用,每次成功匹配变回覆盖
遍历到等号前的字符串去前空格后(/(^\s*)/g),便可变量作为对象的key,清空变量
匹配到'"'则开始保存值value在此遇到'"'结束

方法2️⃣(正则)
handleAttr.js

export default function(attrs) {
let obj = {}
let attrsToArr = attrs.match(/(\w+\=\"[\w|\s|\:]+\")+/g)
console.log(attrs, attrsToArr)
attrsToArr
.forEach(item => {
let arrData =item.split('=')
obj[arrData[0]] = arrData[1]
})
return obj
}

大功告成!!!

posted on   京鸿一瞥  阅读(545)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· winform 绘制太阳,地球,月球 运作规律
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

展开
点击右上角即可分享
微信分享提示