What
DOM定義了通路HTML文檔的标準。
根據DOM标準,HTML文檔内容是由節點構成的,節點之間擁有層級關系,構成了一顆節點樹。
也就是說我們看到的HTML頁面資訊,在記憶體中是存放在一顆節點樹上的。
節點可分為元素節點和文本節點兩大類。
<html>
<head>
<title>文檔标題</title>
</head>
<body>
<a href="www.baidu.com" target="_blank" rel="external nofollow" >我的連結</a>
<h1>我的标題</h1>
</body>
</html>
上面HTML文檔對應的DOM樹如下:
從上圖可以看到,文本節點是目前元素節點的子節點。
Why
HTML詞法解析得到了token,這些token還需要經過文法分析組成節點,并最終建構成一顆完整的DOM樹。這樣程式才能通路HTML文檔内容。
How
HTML詞法解析是按順序解析字元流的,第一個開始标簽對應的結束标簽應該是最後出現的。為了比對開始标簽和結束标簽,DOM樹的建構可以使用棧結構來處理。
**
第一步,我們先定義元素節點和文本節點兩者的資料結構。
// 元素節點
function Element(){
this.type = 'Element';
this.open; // 開始标簽, 比如 <div id="1">
this.name; // 标簽名稱, 全部轉換為小寫
this.close; // 結束标簽, 比如 </div>
this.attributes = []; // 标簽屬性
this.childNodes = []; // 子節點
}
// 文本節點
function Text(value){
this.type = 'Text';
this.value = value;
}
第二步,定義一個parse()函數來接收HTML詞法解析得到的token清單,并用一個棧來存儲處理過程中的節點。
function HTMLSyntaticalParser(){
var stack = [new HTMLDocument];
this.parse = function(token) {
// 1.棧頂就是目前節點
let curNode = stack[stack.length-1];
let node = buildElement(token);
switch(token.type){
case 'StartTag':
// 2.遇到開始标簽就入棧,目前節點就是這個節點的父節點;
curNode.childNodes.push(node);
stack.push(node);
break;
case 'EndTag':
// 3.遇到結束标簽就出棧;
stack.pop();
break;
case 'Text':
// 4.遇到文本節點,如果目前節點是文本節點,則跟文本節點合并,
// 否則入棧成為目前節點的子節點;
if('Element' == curNode.type){
curNode.childNodes.push(node);
stack.push(node);
} else if('Text' == curNode.type){
curNode.value += node.value;
}
}
}
this.getOutput = function(){
// 5.建構過程中,遇到開始标簽就入棧,遇到結束标簽就出棧,最終棧頂就是根節點。
return stack[0];
}
}