用100行代码画出DOM树状构造
这两天写了如许一个小玩具,是一个能够把DOM的树状构造剖析,而且画出来的东西,把HTML代码写到左侧,右侧就会自动天生啦。
源码在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;
}