了解来源
浏览器通过HTTP协议接收到报文之后,我们可以通过split('\r\n');
简单粗暴地把它分隔开,再取用我们需要的部分即可。
但是这样做有一个很大的问题:HTTP传输报文是分段的,我们不能保证每次分隔都作用于完整的报文,因此这种方法不可取,那么应该如何做呢?这里就不得不提到DOM树的概念了,也就是文档对象模型(Document object model),详解可以自行搜索
有穷状态机
浏览器在处理一段HTML字符串的时候,就应用到了有穷状态机的思想,我个人理解是:它将处理的流程分为几个状态,每个状态都负责执行一定的功能,就像工厂的流水线一样,到什么阶段就进行什么操作,只需要将它的逻辑组织好就能实现自动化处理
关于浏览器是如何将一段HTML解析,并且生成一颗DOM树,我想分享以下做法
let htmlStr = `
<html>
<head></head>
<body>
<img />
<span></span>
<div></div>
</body>
</html>
`
// 这段就是我们要处理的HTML字符串
let currentToken = null;
let stack = [{ type: 'document', children: [] }]
// 使用栈来操作匹配标签对,生成一颗DOM树
parse(htmlStr);
console.log(JSON.stringify(stack[0], null, 2));
function emit(token) {
console.log(token);
let top = stack[stack.length - 1];
// console.log(top);
if (token.type === 'startTag') {
let element = {
type: 'element',
tagName: token.tagName,
children: [],
attribute: []
}
stack.push(element);
// if (!top.children) top.children = [];
// console.log(top.children);
top.children.push(element);
} else if (token.type === 'endTag') {
if (token.tagName !== top.tagName) {
throw new Error('tagname match error')
} else{
stack.pop(token);
}
} else if (token.type === 'selfCloseTag') {
let element = {
type: 'element',
tagName: token.tagName,
children: [],
attribute: []
}
top.children.push(element);
}
currentToken = null;
}
这部分就是有穷状态机的应用
function parse(str) {
let state = start;
for (let c of str) {
state = state(c);
}
}
function start(c) {
if (c === '<') {
return tagOpen
} else {
return start
}
}
function tagOpen(c) {
if (c === '/') {
return tagClose
} else if (c.match(/[a-zA-Z]/)) {
currentToken = {
type: 'startTag',
tagName: c
}
return tagName
}
}
function tagName(c) {
if (c.match(/[a-zA-Z]/)) {
currentToken.tagName += c;
return tagName
} else if (c.match(/[/t/r/n ]/)) {
return beforeAttributeName
} else if (c === '>') {
// 拼接结束了
emit(currentToken);
return start
}
}
function beforeAttributeName(c) {
if (c === '/') {
currentToken.type = 'selfCloseTag';
return tagName
}
}
function tagClose(c) {
if (c.match(/[a-zA-Z]/)) {
currentToken = {
type: 'endTag',
tagName: c
}
return tagName
}
}
这是分隔后结果
{ type: 'startTag', tagName: 'html' }
{ type: 'startTag', tagName: 'head' }
{ type: 'endTag', tagName: 'head' }
{ type: 'startTag', tagName: 'body' }
{ type: 'selfCloseTag', tagName: 'img' }
{ type: 'startTag', tagName: 'span' }
{ type: 'endTag', tagName: 'span' }
{ type: 'startTag', tagName: 'div' }
{ type: 'endTag', tagName: 'div' }
{ type: 'endTag', tagName: 'body' }
{ type: 'endTag', tagName: 'html' }
这是处理完成的DOM树,其中element代表它是元素节点
{
"type": "document",
"children": [
{
"type": "element",
"tagName": "html",
"children": [
{
"type": "element",
"tagName": "head",
"children": [],
"attribute": []
},
{
"type": "element",
"tagName": "body",
"children": [
{
"type": "element",
"tagName": "img",
"children": [],
"attribute": []
},
{
"type": "element",
"tagName": "span",
"children": [],
"attribute": []
},
{
"type": "element",
"tagName": "div",
"children": [],
"attribute": []
}
],
"attribute": []
}
],
"attribute": []
}
]
}
希望和大家交流
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!