资讯专栏INFORMATION COLUMN

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

Galence / 3174人阅读

摘要:用行代码画出树状结构这两天写了这样一个小玩具,是一个可以把的树状结构解析,并且画出来的东西,把代码写到左边,右边就会自动生成啦。绘图部分依赖了百度开源的,核心功能的实现只有行代码。如果是或者标签,那么进入相应的状态

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

这两天写了这样一个小玩具,是一个可以把DOM的树状结构解析,并且画出来的东西,把HTML代码写到左边,右边就会自动生成啦。

点这里看DEMO

源码在github · starkwang/DOM-Drawer,使用webpack打了个包。绘图部分依赖了百度开源的 ECharts,核心功能的实现只有100行代码。

核心代码解读

核心代码分成两部分,tokenizer 和 parser,流程的本质上是一个最最最最简单的编译器前端。

我们期望是把类似这样的HTML字符串:

解析成这样的对象:

{
    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 == "
" || 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] == "-" ? "" : 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://www.ucloud.cn/yun/86131.html

相关文章

  • 前端每日实战:141# 视频演示如何 CSS 的 Grid 布局创作一枚小狗邮票

    摘要:效果预览按下右侧的点击预览按钮可以在当前页面预览,点击链接可以全屏预览。可交互视频此视频是可以交互的,你可以随时暂停视频,编辑视频中的代码。 showImg(https://segmentfault.com/img/bVbhqjK?w=400&h=300); 效果预览 按下右侧的点击预览按钮可以在当前页面预览,点击链接可以全屏预览。 https://codepen.io/comehop...

    yintaolaowanzi 评论0 收藏0
  • 前端每日实战:141# 视频演示如何 CSS 的 Grid 布局创作一枚小狗邮票

    摘要:效果预览按下右侧的点击预览按钮可以在当前页面预览,点击链接可以全屏预览。可交互视频此视频是可以交互的,你可以随时暂停视频,编辑视频中的代码。 showImg(https://segmentfault.com/img/bVbhqjK?w=400&h=300); 效果预览 按下右侧的点击预览按钮可以在当前页面预览,点击链接可以全屏预览。 https://codepen.io/comehop...

    Baoyuan 评论0 收藏0
  • JavaScript 编程精解 中文第三版 十七、在画布上绘图

    摘要:贝塞尔曲线方法可以绘制一种类似的曲线。不同的是贝塞尔曲线需要两个控制点而不是一个,线段的每一个端点都需要一个控制点。下面是描述贝塞尔曲线的简单示例。 来源:ApacheCN『JavaScript 编程精解 中文第三版』翻译项目原文:Drawing on Canvas 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 部分参考了《JavaScript 编程精解(第 2...

    habren 评论0 收藏0
  • DOM操作笔记

    摘要:它实际上等于清除当前文档流,重新写入内容方法用于关闭方法所新建的文档。如果页面已经渲染完成关闭了,再调用方法,它会先调用方法,擦除当前文档所有内容,然后再写入我们的页面渲染的时候就会去打开一个文档流,当渲染绘制结束,就关闭这个文档流。 一、DOM简介 1、定义: DOM 是 JavaScript 操作网页的接口,全称为文档对象模型(Document Object Model)。 2、作...

    newtrek 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<