Vue源码AST抽象语法树 - 指针- 递归 - 栈 - 正则表达式 - 手写实现parse
Posted YK菌
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vue源码AST抽象语法树 - 指针- 递归 - 栈 - 正则表达式 - 手写实现parse相关的知识,希望对你有一定的参考价值。
https://gitee.com/ykang2020/vue_learn
1. 抽象语法树是什么
模板语法
抽象语法树(Abstract Syntax Tree)本质上就是一个JS对象
抽象语法树与我们之前学的虚拟节点有什么关系呢
2. 知识储备
2.1 指针
// 试寻找字符串中,连续重复次数最多的字符
let str = 'aaaaaaabbbbbbbbbbbccccccccccccccccccccccddddddddd'
let i = 0
let j = 1
let maxRepeatCount = 0
let maxRepeatChar = ''
while (i <= str.length - 1) {
if (str[i] !== str[j]) {
console.log(`${i}和${j}之间的字母${str[i]}连续重复了${j-i}次`)
if (j - i > maxRepeatCount) {
maxRepeatCount = j - i
maxRepeatChar = str[i]
}
i = j
}
j++
}
console.log(`字母${maxRepeatChar}连续重复次数最多,重复了${maxRepeatCount}次`)
2.2 递归
直接暴力递归
function fib(n) {
return n === 0 || n === 1 ? 1 : fib(n-1) + fib(n-2)
}
加入缓存对象
let cache = {}
function fib(n) {
if(cache.hasOwnProperty(n)){
return cache[n]
}
let value = n === 0 || n === 1 ? 1 : fib(n-1) + fib(n-2)
cache[n] = value
return value
}
let arr = [1, 2, 3, [4, 5, [6, 7], 8], 9, [10, 11]]
function convert(arr){
let result = []
arr.forEach(item => {
if(typeof item === 'number'){
result.push({
value: item
})
}else if (Array.isArray(item)) {
result.push({
children: convert(item)
})
}
})
return result
}
let res = {children: convert(arr)}
console.log(res)
let arr = [1, 2, 3, [4, 5, [6, 7], 8], 9, [10, 11]]
function convert(item){
if(typeof item === 'number'){
return {value: item}
}else if (Array.isArray(item)) {
return {
children: item.map(_item=>convert(_item))
}
}
}
console.log(convert(arr))
2.3 正则表达式
replace 删除字符串中数字
> 'awerfa453q5325fd5234rfsdf'.replace(/\\d/g, '')
< "awerfaqfdrfsdf"
search 查找第一个匹配到的位置
> 'awerfa453q5325fd5234rfsdf'.search(/\\d/)
< 6
> 'awerfa453q5325fd5234rfsdf'.search(/\\d/g)
< 6
match 匹配
> 'awerfa453q5325fd5234rfsdf'.match(/\\d/)
< ["4", index: 6, input: "awerfa453q5325fd5234rfsdf", groups: undefined]
> 'awerfa453q5325fd5234rfsdf'.match(/\\d/g)
< (11) ["4", "5", "3", "5", "3", "2", "5", "5", "2", "3", "4"]
match 捕获 分组
> '23[abc]'.match(/^\\d+\\[/)
< ["23[", index: 0, input: "23[abc]", groups: undefined]
> '23[abc]'.match(/(^\\d+)\\[/)
< (2) ["23[", "23", index: 0, input: "23[abc]", groups: undefined]
test
2.4 栈
准备两个栈
一个用来存放数字
一个用来存放字符串
【解题思路】
function smartRepeat(templateStr) {
let i = 0
let stackNumber = []
let stackString = []
let rest = templateStr
while (i < templateStr.length - 1) {
// 剩余部分
rest = templateStr.substring(i);
// 看剩余部分是不是以数字和[开头
if (/^\\d+\\[/.test(rest)) {
// 得到数字
let times = Number(rest.match(/^(\\d+)\\[/)[1])
// 将数字和空字符串分别入对应的栈
stackNumber.push(times)
stackString.push('')
// 指针向后移动数字的位数 ,再加上[这一位
i += times.toString().length + 1
} else if (/^\\w+\\]/.test(rest)) {
// 剩余部分是字母数字下划线和]开头的
// 捕获[]中的内容
let word = rest.match(/^(\\w+)\\]/)[1]
// 将字符栈顶改为当前字母
stackString[stackString.length - 1] = word
// 指针右移,移动到]的位置
i += word.length
} else if (rest[0] === ']') {
let word = stackString.pop()
let times = stackNumber.pop()
// 将word重复times次,插入到栈顶字符串后面
let newStr = word.repeat(times)
stackString[stackString.length - 1] += newStr
i++
}
console.log(i, stackNumber, stackString)
}
return stackString[0].repeat(stackNumber[0])
}
let result = smartRepeat('2[4[abc]3[b]]')
console.log(result)
3. 实现AST
源码中的parse函数就是将html变成AST
index.js
import parse from "./parse";
let templateString = `<div>
<h3 class="aa bb cc" v-on="xxx" id="mybox">你好</h3>
<ul>
<li>A</li>
<li>B</li>
<li>C</li>
</ul>
</div>`;
const AST = parse(templateString);
console.log(AST);
parse.js
import parseAttrsString from "./parseAttrsString";
/**
* parse函数
* @param {*} templateString
* @returns
*/
export default function parse(templateString) {
// 定义一个指针
let i = 0;
// 剩余部分
let rest = "";
// 开始标记正则
let startRegExp = /^\\<([a-z1-6]+)(\\s[^\\<]+)?\\>/;
// 结束标记正则
let endRegExp = /^\\<\\/([a-z1-6]+)\\>/;
// 结束标记之前的文字【前面不是是<】
let wordRegExp = /^([^\\<]+)\\<\\/[a-z0-9]+\\>/;
// 准备两个栈
let stack1 = []; // 辅助栈,存标签名
let stack2 = [{ chilren: [] }];
while (i < templateString.length - 1) {
rest = templateString.substring(i);
// 识别开始标签
if (startRegExp.test(rest)) {
let tag = rest.match(startRegExp)[1];
let attrsString = rest.match(startRegExp)[2];
console.log(i, "检测到开始标记", tag);
const attrsStringLength = attrsString == null ? 0 : attrsString.length;
// 将开始标记 入栈1
stack1.push(tag);
// 处理标签属性
const attrsArray = parseAttrsString(attrsString);
// 将空数组 入栈2
stack2.push({ tag: tag, chilren: [], attrs: attrsArray });
i += tag.length + 2 + attrsStringLength;
} else if (endRegExp.test(rest)) {
// 遇见结束标签
let tag = rest.match(endRegExp)[1];
console.log(i, "检测到结束标记", tag);
let pop_tag = stack1.pop();
// 此时tag一定与stack1的栈顶相同
if (tag === pop_tag) {
let pop_arr = stack2.pop();
if (stack2.length > 0) {
stack2[stack2.length - 1].chilren.push(pop_arr);
}
} else {
throw new Error(pop_tag + "标签没有封闭好");
}
i += tag.length + 3;
} else if (wordRegExp.test(rest)) {
let word = rest.match(wordRegExp)[1];
// 看word是不是全空
if (!/^\\s+$/.test(word)) {
// 不为空
console.log(i, "检测到文字", word);
// 改变stack2的栈顶元素
stack2[stack2.length - 1].chilren.push({ text: word, type: 3 });
}
i += word.length;
} else {
i++;
}
}
return stack2[0].chilren[0];
}
处理 标签属性
parseAttrsString.js
/**
* 将attrsString变为数组返回
* @param {*} attrsString
*/
export default function parseAttrsString(attrsString) {
let result = [];
// 当前是否在引号内
let isQuote = false;
let point = 0;
if (attrsString === undefined) return [];
for (let i = 0; i < attrsString.length; i++) {
let char = attrsString[i];
if (char === '"') {
isQuote = !isQuote;
} else if (char === " " && !isQuote) {
// 是空格且不在引号之中
let str = attrsString.substring(point, i);
// 不是纯空格
if (!/^\\s*$/.test(str)) {
result.push(str.trim());
point = i;
}
}
}
// 循环结束之后,最后一个也要加进去
result.push(attrsString.substring(point).trim());
result = result.map((item) => {
// 根据等号拆分
const o = item.match(/^(.+)="(.+)"$/);
return {
name: o[1],
value: o[2],
};
});
return result;
}
以上是关于Vue源码AST抽象语法树 - 指针- 递归 - 栈 - 正则表达式 - 手写实现parse的主要内容,如果未能解决你的问题,请参考以下文章