一些前端可能用到的函数

56 阅读3分钟

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;
}

返回的结果如下所示:

image.png

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)
        }
    }
}