用100行代码画出DOM树状构造

用100行代码画出DOM树状构造

这两天写了如许一个小玩具,是一个能够把DOM的树状构造剖析,而且画出来的东西,把HTML代码写到左侧,右侧就会自动天生啦。

点这里看DEMO

源码在github · starkwang/DOM-Drawer,运用webpack打了个包。画图部份依靠了百度开源的 ECharts,中心功用的完成只要100行代码。

中心代码解读

中心代码分红两部份,tokenizer 和 parser,流程的本质上是一个最最最最简朴的编译器前端。

我们希冀是把相似如许的HTML字符串:

<div>
    <p></p>
    <img>
    <a></a>
</div>

剖析成如许的对象:

{
    name : 'div',
    children : [
        {
            name : 'p',
            childern : []
        },
        {
            name : 'img',
            childern : []
        },
        {
            name : 'a',
            childern : []
        },
    ]
}

Tokenizer

tokenizer 担任把 HTML 字符串分割成一个由单词、特别符号构成的数组(去掉空格、换行符、缩进),末了返回这个数组给 parser 举行剖析。

module.exports = tokenizer;
function tokenizer(content) {
    //效果数组
    var result = [];  
    
    //特别符号的鸠合
    var symbol = ['{', '}', ':', ';', ',', '(', ')', '.', '#', '~', , '<', '>', '*', '+', '[', ']', '=', '|', '^']; 
    
    //是不是在字符串中,假如是的话,要保存换行、缩进、空格 
    var isInString = false;
    
    //当前的单词栈
    var tmpString = '';
    
    
    for (var i = 0; i < content.length; i++) {
        //逐一读取字符
        var t = content[i];
        
        //当读取到引号时,进入字符串状况
        if (t == '\'' || t == '\"') {
            if (isInString) {
                tmpString += t;
                isInString = false;
                result.push(tmpString);
                tmpString = '';
            } else {
                tmpString += t;
                isInString = true;
            }
            continue;
        }
        
        
        if (isInString) {
            //字符串状况
            tmpString += t;
        } else {
            //非字符串状况
            
            if (t == '\n' || t == ' ' || t == '    ') {
                //假如读到了换行、空格或许tab,那末把当前单词栈中的字符作为一个单词push到效果数组中,并清零单词栈
                if (tmpString.length != 0) {
                    result.push(tmpString);
                    tmpString = '';
                }
                continue;
            }
            if (symbol.indexOf(t) != -1) {
                    //假如读到了特别符号,那末把当前单词栈中的字符作为一个单词push到效果数组中,清零单词栈,再把这个特别符号放进效果数组
                if (tmpString.length != 0) {
                    result.push(tmpString);
                    tmpString = '';
                }
                result.push(t);
                continue;
            }
            //不然把字符推入单词栈中
            tmpString += t;
        }
    }
    return result;
}

Parser

parser担任逐一读取 tokenizer 天生的单词序列,而且剖析成一个树形构造,这里用到了相似状况机的头脑。

module.exports = parser;
function parser(tokenArray) {

    //等下我们要从单词序列中过滤出HTML标签
    var tagArray = [];
    
    //节点构成的栈,用于纪录状况
    var nodeStack = [];
    
    //根节点
    var nodeTree = {
        name: 'root',
        children: []
    };
    
    //是不是在script、style标签内部
    var isInScript = false,
        isInStyle = false;
    
    //先把根节点推入节点栈
    nodeStack.push(nodeTree);
    
    //一大堆单词序列中过滤出HTML标签,注重这里没有考虑到script、style中的特别字符
    tokenArray.forEach(function(item, index) {
        if (item == '<') {
            tagArray.push(tokenArray[index + 1]);
        }
    })
    
    //HTML规范中自关闭的标签
    var selfEndTags = ['img', 'br', 'hr', 'col', 'area', 'link', 'meta', 'frame', 'input', 'param'];
    
    
    tagArray.forEach(function(item, index) {
        //逐一读取标签
        if (item[0] == '!' || selfEndTags.indexOf(item) != -1) {
            //自关闭标签、解释、!DOCTYPE
            nodeStack[nodeStack.length - 1].children.push({
                name: item[0] == '!' && item[1] == '-' && item[2] == '-' ? '<!--comment-->' : item,
                children: []
            });
        } else {
            //一般标签
            if (item[0] != '/') {
                //一般标签头
                if (!isInScript && !isInStyle) {
                    //假如不在script或许style标签中,向节点栈尾部的children中到场这个节点,并推入这个节点,让它成为节点栈的尾部
                    var newNode = {
                        name: item,
                        children: []
                    }
                    nodeStack[nodeStack.length - 1].children.push(newNode);
                    nodeStack.push(newNode);
                }
                
                //假如是script或许style标签,那末进入响应的状况
                if (item == 'script') {
                    isInScript = true;
                }
                if (item == 'style') {
                    isInStyle = true;
                }
            } else {
                //一般标签尾
                if (item.split('/')[1] == nodeStack[nodeStack.length - 1].name) {
                    //假如这个标签和节点栈尾部的标签雷同,那末以为这个节点停止,节点栈推出。
                    nodeStack.pop();
                }
                
                //假如是script或许style标签,那末进入响应的状况
                if (item.split('/')[1] == 'script') {
                    isInScript = false;
                }
                if (item.split('/')[1] == 'style') {
                    isInStyle = false;
                }
            }
        }
    })
    return nodeTree;
}
    原文作者:王伟嘉
    原文地址: https://segmentfault.com/a/1190000003937571
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞