1.将vue组件转为AST语法树
function parseHTML(html) { //html最开始肯定是一个<
const ncname = `[a-zA-Z][\\-\\.0-9_a-zA-Z]*`;
const qnameCapture = `((?:${ncname}\\:)?${ncname})`;
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 他匹配到的分组是一个 标签名 <xxx 匹配到的是开始 标签的名字
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配的是</xxxx> 最终匹配到的分组就是结束标签的名字
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性 color = "xxx" a='bb' c=d
// 第一个分组就是属性的key value 就是 分组3/分组4/分组五
const startTagClose = /^\s*(\/?)>/; // <div> <br/>
const defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; // {{ asdsadsa }} 中间可以换行回车,匹配到的内容就是我们表达式的变量
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;
const stack = []; //定义栈用于存放元素
let currentParent; //指向的是栈中的最后一个
let root; //根节点
//最终需要转化成一棵抽象语法树
function createASTElement(tag,attrs){
return{
tag,
type:ELEMENT_TYPE,
children:[], //开始标签时还不知道孩子是谁
attrs,
parent:null
}
}
// 利用栈型结构来构造一棵树
function start(tag,attrs){
let node = createASTElement(tag,attrs); //创造一个ast节点
if(!root){ //看一下是否是空树
root = node; //如果为空则当前是树的根节点
}
if(currentParent){
node.parent = currentParent; //只赋予了parent属性
currentParent.children.push(node); //还需要让父亲记住自己
}
stack.push(node);
currentParent = node; //currentParent为栈中的最后一个
}
function chars(text){ //文本直接放到当前指向的节点中
text = text.replace(/\s/g,''); //如果空格超过2就删除2个以上
text && currentParent.children.push({
type:TEXT_TYPE,
text,
parent:currentParent
});
}
function end(tag){
let node = stack.pop(); //弹出最后一个,检验标签是否合法
currentParent = stack[stack.length-1];
}
//截取匹配成功的部分
function advance(n) {
html = html.substring(n);
}
function parseStartTag() {
const start = html.match(startTagOpen);
// console.log(start) ---0: "<div" 1: "div"
if (start) {
const match = {
tagName: start[1], //标签名(分组就是标签名)
attrs: [] //还需要有标签的属性
}
advance(start[0].length);
// console.log(match,html)
//如果不是开始标签的结束,就一直匹配,且每次匹配同时也希望把这个属性保留起来
let attr, end //end就是>
while (!(end = html.match(startTagClose)) && (attr = html.match(attribute))) {
advance(attr[0].length);
// console.log(attr) 0:' id="app"', 1:'id', 2:'=', 3:'app',
match.attrs.push({name:attr[1],value:attr[3] || attr[4] || attr[5] ||true})
}
if (end) {
advance(end[0].length)
}
return match;
}
// console.log(html)
return false; //不是开始标签
};
while (html) {
// 如果textEnd 为0,那说明是一个开始标签或者结束标签 hello</div>
// 如果textEnd >0,说明就是文本的结束位置
let textEnd = html.indexOf('<'); //如果indexOf中的索引是0 则说明是个标签,vue3可以不是标签开始,在template里直接写字符串
if (textEnd == 0) {
const startTagMatch = parseStartTag(); //开始标签的匹配结果
if(startTagMatch){ //解析到的开始标签
start(startTagMatch.tagName,startTagMatch.attrs)
continue
}
let endTagMatch = html.match(endTag);
if(endTagMatch){ //结束标签
advance(endTagMatch[0].length);
end(endTagMatch[1]);
// console.log(endTagMatch) 0: "</div>" 1:div
continue;
}
}
if(textEnd > 0){ //有文本
let text = html.substring(0,textEnd); //从0截取到textEnd文本内容
if(text){
chars(text);
advance(text.length);//解析到的文本
}
}
}
return root;
}
返回的结果如下所示:
2.获取树的深度
// 获取树的深度
function getTreeDeep(treeData) {
let arr = [];
arr.push(treeData);
let depth = 0;
while (arr.length > 0) {
let temp = [];
for (let i = 0; i < arr.length; i++) {
temp.push(arr[i]);
}
arr = [];
for (let i = 0; i < temp.length; i++) {
if (temp[i].children && temp[i].children.length > 0) {
for (let j = 0; j < temp[i].children.length; j++) {
arr.push(temp[i].children[j]);
}
}
}
if (arr.length >= 0) {
depth++;
}
}
return depth;
}
3.获取树中所有节点个数
// 提取树的所有节点,最终树的所有节点都会存入传入的nodeList数组中
function getAllTreeNode(treeData, nodeList) {
// 判断是否为数组
if (Array.isArray(treeData)) {
treeData.forEach(item => {
if (item.children && item.children.length > 0) {
nodeList.push(item)
getAllTreeNode(item.children, nodeList)
} else {
nodeList.push(item)
}
})
} else {
if (treeData.children && treeData.children.length > 0) {
nodeList.push(treeData)
getAllTreeNode(treeData.children, nodeList)
} else {
nodeList.push(treeData)
}
}
}