Vue源码之AST抽象语法树
AST抽象语法树全称Abstract Sybtax Tree,简称就是AST,其作用就是Vue在解析模板时,会把模板编译成抽象语法树,在进行diff最小量更新算法等
AST抽象语法树是Vue渲染引擎的第一道工序
他的结果类似于原生js的对象结构
AST抽象语法树底层采用了大量的正则表达式以及栈的结合使用
接下来手撸一个AST抽象语法树
1. 配置webpack环境,实现模块化编程
环境为
"webpack": "^4.46.0",
"webpack-cli": "^3.3.12"
"webpack-dev-server": "^3.11.3"
webpack.config.js文件配置
const path = require("path")
module.exports = {
mode: "development",
entry: './src/index.js',
output: {
filename: "bundle.js"
},
devServer: {
contentBase: path.join(__dirname, "www"),
compress: false,
port: 8080,
publicPath: "/xuni/"
}
}
2. index.js入口文件
import parse from "./parse";
let templateString = `<div>
<h3 class="demo" id="cmd">Hello</h3>
<ul>
<li id="bid">A</li>
<li>B</li>
<li>C</li>
</ul>
</div>`
const AST = parse(templateString)
console.log(AST)
准备一个模板字符串,我们把他转化为AST抽象语法树
该文件最核心的函数就是parse函数,在Vue的源码中转化为AST抽象语法树函数的名字也是parse
3. parse函数
import parseAttrsString from "./parseAttrsString";
export default function parse(templateString) {
//Pointer
let index = 0
//Remaining string
let restString = ""
//Start tag
let startRegExp = /^<([a-z]+[1-6]?)(\s[^<]+)?>/
//The end tag
let endRegExp = /^<\/([a-z]+[1-6]?)>/
//Define two stacks one to store labels and one to store content
let stackTag = []
let stackLetter = [{children: []}]
//Grab the text before the end tag
let wordRegExp = /^([^<]+)<\/[a-z]+[1-6]?>/
while (index < templateString.length - 1) {
restString = templateString.substring(index)
if (startRegExp.test(restString)) {
let tag = restString.match(startRegExp)[1]
let attrsString = restString.match(startRegExp)[2]
//Push the tag and the object onto the stack, respectively
stackTag.push(tag)
stackLetter.push({'tag': tag, 'children': [], "attrs":
parseAttrsString(attrsString)})
const attrsStringLength = attrsString !== undefined ? attrsString.length : 0
index += tag.length + 2 + attrsStringLength
} else if (endRegExp.test(restString)) {
let tag = restString.match(endRegExp)[1]
let pop_tag = stackTag.pop()
let pop_letter = stackLetter.pop()
if (tag === pop_tag) {
if (stackLetter.length > 0) {
stackLetter[stackLetter.length - 1].children.push(pop_letter)
}
} else {
throw new Error(`${pop_tag} tag is not closed`)
}
index += tag.length + 3
} else if (wordRegExp.test(restString)) {
let word = restString.match(wordRegExp)[1]
if (!/^\s+$/.test(word)) {
//Text is detected on top of stack
stackLetter[stackLetter.length - 1].children.push({"text": word, 'type':
3})
}
index += word.length
} else {
index++
}
}
return stackLetter[0].children[0]
}
实现原理
首先准备2个栈,一个存放标签,一个存放内容,再准备一个指针,使用指针迭代一遍模板字符串
在迭代的同时一直进行正则匹配,如果遇到标签开始标记进行压栈,且将内容压入另一个栈中
当遇到结束标记时进行双弹栈,并将内容栈的栈顶元素压入栈顶的第二个元素中
就形成了树状绑定
4. parseAttrsString
为了让抽象语法树识别选择器,就有了parseAttrsString函数
//To deal with attrsString
export default function parseAttrsString(attrsString) {
if (attrsString === undefined) return []
//Whether the current is in quotation marks
let isYinHao = false
//breakpoint
let point = 0
//result array
let result = []
for (let i = 0; i < attrsString.length; i++) {
let char = attrsString[i]
if (char === '"') {
isYinHao = !isYinHao
} else if (char === " " && !isYinHao) {
if (!/^\s*?$/.test(attrsString.substring(point, i))) {
result.push(attrsString.substring(point, i).trim())
point = i
}
}
}
result.push(attrsString.substring(point))
result = result.map(item => {
const o = item.match(/^(.+)=(.+)$/)
return {
name: o[1],
value: o[2].substring(1, o[2].length - 1)
}
})
return result
}
实现原理
准备一个结果数组,利用正则表达式匹配并且分开空格,最后利用映射,形成attrs对象,并压入栈中并返回结果数组
5. 最后
npm run dev
代码已上传至github仓库
https://github.com/Bald-heads/ASTByMyself.git