这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战
Vue
将HTML
转译为AST
大致过程:通过正则不断的匹配HTML
的标签、属性、注释等,根据不同的类型进行不同的处理,比如匹配到是标签类型,那么此时需要创建一个对象,将其标签名赋值给tag
属性,设置其type
为1等,因为其是一个标签类型,需要匹配其属性,若是存在需要将属性依次存储到attrs
中,若是这个标签存在子元素,需要将子的信息存储在childred
中,同时标记parent
是谁,这样一个HTML
标签对应的AST
便创建完成了
解析入口
将html
转换为AST
的整个流程封装在parseHTML
函数中
function parseHTML(html) {
let root = null; // ast树的树根
let currentParent = null; // 标识当前的parent
// 定义一个栈数据结构,将解析的标签进行存取
let stack = [];
// 将节点类型初始化
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;
}
复制代码
首先将一些常用的变量进行定义并初始化,接下来便是对html
进行正则匹配,可参考下面代码和注释结合理解
// 不停的解析HTML字符串
while (html) {
let textEnd = html.indexOf("<");
// 标签相关处理
if (textEnd === 0) {
// 匹配到是开始标签
let startTagMatch = parseeStartTag();
if (startTagMatch) {
start(startTagMatch.tagName, startTagMatch.attrs);
continue; // 如果开始标签匹配结束了 继续下一次匹配
}
// 匹配到是结束标签
let endTagMatch = html.match(endTag);
if (endTagMatch) {
advance(endTagMatch[0].length);
end(endTagMatch[1]);
continue;
}
}
// 文本相关处理
let text;
if (textEnd >= 0) {
text = html.substring(0, textEnd);
}
if (text) {
advance(text.length);
chars(text);
}
}
复制代码
通过代码可得知其就是不断通过正则匹配html
进行处理,每次处理结束便将html
截取进行下一次的匹配处理,一直到html
为空才结束,每次处理开始,使用textEnd
判断匹配到的是标签还是文本
标签
若是标签就进入到标签的处理逻辑,标签分为开始标签和结束标签
开始标签
const parseeStartTag = () => {
let start = html.match(startTagOpen);
// 匹配到内容
if (start) {
let match = {
tagName: start[1],
attrs: [],
};
advance(start[0].length); // 将标签删除
let end, attr;
// 匹配到标签结束 && 有属性
// 将属性进行解析
while (
!(end = html.match(startTagClose)) &&
(attr = html.match(attribute))
) {
advance(attr[0].length); // 将属性移除
match.attrs.push({
name: attr[1],
value: attr[3] || attr[4] || attr[5],
});
}
// 结束标签
if (end) {
// 去掉开始标签的 >
advance(end[0].length);
return match;
}
}
};
复制代码
因为在开始标签内会填充很多属性,函数内部需要将标签名和属性进行提取并返回,然后对其进行处理,逻辑封装至start
函数中
const start = (tagName, attrs) => {
// 遇到开始标签 创建一个 ast 元素
let element = createASTElement(tagName, attrs);
if (!root) {
root = element;
}
// 把当前元素标记为 parent
currentParent = element;
stack.push(element); // 将开始标签 存放到栈中
};
复制代码
结束标签
匹配其是到结束标签时,将逻辑封装到end
函数中
const end = (tagName) => {
let element = stack.pop();
// 标识当前这个元素是属于parent的children
currentParent = stack[stack.length - 1];
if (currentParent) {
element.parent = currentParent;
currentParent.children.push(element); // 实现一个树的父子关系
}
};
复制代码
文本节点
对于文本节点的处理比较简单,直接创建对应的AST
返回即可,将逻辑封装到chars
函数中
const chars = (text) => {
text = text.replace(/\s/g, "");
if (text) {
currentParent.children.push({
text,
type: TEXT_TYPE,
});
}
};
复制代码
至此parseHTML
的核心流程完成,拿到AST
之后,接下来便是准备如何根据AST
生成render
函数