1. 程式人生 > >javascript學習筆記:DOM節點關系和操作

javascript學習筆記:DOM節點關系和操作

seq 文本節點 文檔 use true 添加節點 check iss pen

0x01:前面的話

DOM可以將任何HTML描繪成一個由多層節點構成的結構。節點分為12種不同類型,每種類型分別表示文檔中不同的信息及標記。每個節點都擁有各自的特點、數據和方法,也與其他節點存在某種關系。節點之間的關系構成了層次,而所有頁面標記則表現為一個以特定節點為根節點的樹形結構。本文將詳細描述DOM間的節點關系和基礎的DOM操作。
技術分享圖片
節點中的各種關系可以用傳統的家族關系來描述,相當於把文檔樹比喻成家譜。接下來,將把DOM節點關系分為屬性和方法兩部分進行詳細說明。

0x02:屬性

父級屬性
parentNode

  每個節點都有一個parentNode屬性,該屬性指向文檔樹中的父節點。對於一個節點來說,它的父節點只可能是三種類型:element節點、document節點和documentfragment節點。如果不存在,則返回null。

    <div id="myDiv"></div>
<script>
console.log(myDiv.parentNode);//body
console.log(document.body.parentNode);//html
console.log(document.documentElement.parentNode);//document
console.log(document.parentNode);//null
</script>
<div id="myDiv"></div>
<script>
var myDiv = document.getElementById(‘myDiv‘);
console.log(myDiv.parentNode);//body
var fragment = document.createDocumentFragment();
fragment.appendChild(myDiv);
console.log(myDiv.parentNode);//document-fragment
</script>

parentElement

  與parentNode屬性不同的是,parentElement返回的是父元素節點
<div id="myDiv"></div>
<script>
console.log(myDiv.parentElement);//body
console.log(document.body.parentElement);//html
console.log(document.documentElement.parentElement);//null
console.log(document.parentElement);//null
</script>

[註意]在IE瀏覽器中,只有Element元素節點才有該屬性,其他瀏覽器則是所有類型的節點都有該屬性

<div id="test">123</div>
<script>
//IE瀏覽器返回undefined,其他瀏覽器返回<div id="test">123</div>
console.log(test.firstChild.parentElement);
//所有瀏覽器都返回<body>
console.log(test.parentElement);
</script>

子級屬性
childNodes

  childNodes是一個只讀的類數組對象NodeList對象,它保存著該節點的第一層子節點

<ul id="myUl"><li><div></div></li></ul>
<script>
var myUl = document.getElementById(‘myUl‘);
//結果是只包含一個li元素的類數組對象[li]
console.log(myUl.childNodes);
</script>

children

  children是一個只讀的類數組對象HTMLCollection對象,但它保存的是該節點的第一層元素子節點

<div id="myDiv">123</div>
<script>
var myDiv = document.getElementById(‘myDiv‘);
//childNodes包含所有類型的節點,所以輸出[text]
console.log(myDiv.childNodes);
//children只包含元素節點,所以輸出[]
console.log(myDiv.children);
</script>

childElementCount

  返回子元素節點的個數,相當於children.length

  [註意]IE8-瀏覽器不支持

<`ul` id="myUl">
    <li></li>
    <li></li>
</ul>
<script>
var myUl = document.getElementById(‘myUl‘);
console.log(myUl.childNodes.length);//5,IE8-瀏覽器返回2,因為不包括空文本節點
console.log(myUl.children.length);//2
console.log(myUl.childElementCount);//2,IE8-瀏覽器返回undefined
</script>

firstChild

  第一個子節點

lastChild

  最後一個子節點

firstElementChild

  第一個元素子節點

lastElementChild

  最後一個元素子節點 

  上面四個屬性,IE8-瀏覽器和標準瀏覽器的表現並不一致。IE8-瀏覽器不考慮空白文本節點,且不支持firstElementChild和lastElementChild

//ul標簽和li標簽之間有兩個空白文本節點,所以按照標準來說,ul的子節點包括[空白文本節點、li元素節點、空白文本節點]。但在IE8-瀏覽器中,ul的子節點只包括[li元素節點]
<ul>
<li></li>
</ul>

<ul id="list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
</ul>
<script>
console.log(list.firstChild);//標準瀏覽器中返回空白文本節點,IE8-瀏覽器中返回<li>1</li>
console.log(list.lastChild);//標準瀏覽器中返回空白文本節點,IE8-瀏覽器中返回<li>3</li>
console.log(list.firstElementChild);//標準瀏覽器中<li>1</li>,IE8-瀏覽器中返回undefined
console.log(list.lastElementChild);//標準瀏覽器中<li>3</li>,IE8-瀏覽器中返回undefined
</script>

同級屬性
nextSibling

  後一個節點

previousSibling

  前一個節點

nextElementSibling

  後一個元素節點

previousElementSibling

  前一個元素節點

  與子級屬性類似,上面四個屬性,IE8-瀏覽器和標準瀏覽器的表現並不一致。IE8-瀏覽器不考慮空白文本節點,且不支持nextElementSibling和previousElementSibling

<ul>
    <li>1</li>
    <li id="myLi">2</li>
    <li>3</li>
</ul>
<script>
var myLi = document.getElementById(‘myLi‘);
console.log(myLi.nextSibling);//空白節點,IE8-瀏覽器返回<li>3</li>
console.log(myLi.nextElementSibling);//<li>3</li>,IE8-瀏覽器返回undefined
console.log(myLi.previousSibling);//空白節點,IE8-瀏覽器返回<li>1</li>
console.log(myLi.previousElementSibling);//<li>1</li>,IE8-瀏覽器返回undefined
</script>

方法

包含方法
hasChildNodes()

  hasChildNodes()方法在包含一個或多個子節點時返回true,比查詢childNodes列表的length屬性更簡單

<div id="myDiv">123</div>
<script>
var myDiv = document.getElementById(‘myDiv‘);
console.log(myDiv.childNodes.length);//1
console.log(myDiv.hasChildNodes());//true
</script>

<div id="myDiv"></div>
<script>
var myDiv = document.getElementById(‘myDiv‘);
console.log(myDiv.childNodes.length);//0
console.log(myDiv.hasChildNodes());//false
</script>

contains()

  contains方法接受一個節點作為參數,返回一個布爾值,表示參數節點是否為當前節點的後代節點。參數為後代節點即可,不一定是第一層子節點 


<div id="myDiv">
    <ul id="myUl">
        <li id="myLi"></li>
        <li></li>
    </ul>
</div>
<script>
console.log(myDiv.contains(myLi));//true
console.log(myDiv.contains(myUl));//true
console.log(myDiv.contains(myDiv));//true
</script>

  [註意]IE和safari不支持document.contains()方法,只支持元素節點的contains()方法

//IE和safari報錯,其他瀏覽器返回true
console.log(document.contains(document.body));

關系方法
compareDocumentPosition()

  compareDocumentPosition方法用於確定節點間的關系,返回一個表示該關系的位掩碼

000000    0     兩個節點相同
000001    1     兩個節點不在同一個文檔(即有一個節點不在當前文檔)
000010    2     參數節點在當前節點的前面
000100    4     參數節點在當前節點的後面
001000    8     參數節點包含當前節點
010000    16    當前節點包含參數節點
100000    32    瀏覽器的私有用途
<div id="myDiv">
    <ul id="myUl">
        <li id="myLi1"></li>
        <li id="myLi2"></li>
    </ul>
</div>
<script>

//20=16+4,因為myUl節點被myDiv節點包含,也位於myDiv節點的後面
console.log(myDiv.compareDocumentPosition(myUl));

//10=8+2,因為myDiv節點包含myUl節點,也位於myUl節點的前面
console.log(myUl.compareDocumentPosition(myDiv));

//0,兩個節點相同
console.log(myDiv.compareDocumentPosition(myDiv));

//4,myLi2在myLi1節點的後面
console.log(myLi1.compareDocumentPosition(myLi2));

//2,myLi1在myLi2節點的前面
console.log(myLi2.compareDocumentPosition(myLi1));
</script>

isSameNode()和isEqualNode()

  這兩個方法都接受一個節點參數,並在傳入節點與引用節點相同或相等時返回true

  所謂相同(same),指的是兩個節點引用的是同一個對象

  所謂相等(equal),指的是兩個節點是相同的類型,具有相等的屬性(nodeName、nodeValue等等),而且它們的attributes和childNodes屬性也相等(相同位置包含相同的值)

  [註意]firefox不支持isSameNode()方法,而IE8-瀏覽器兩個方法都不支持

<script>
var div1 = document.createElement(‘div‘);
div1.setAttribute("title","test");
var div2 = document.createElement(‘div‘);
div2.setAttribute("title","test");
console.log(div1.isSameNode(div1));//true
console.log(div1.isEqualNode(div2));//true
console.log(div1.isSameNode(div2));//false
</script>

0x03:DOM節點操作

一般地,提起操作會想到“增刪改查”這四個字,而DOM節點操作也類似地對應於此,接下來將詳細介紹DOM的節點操作方法。
前提
  DOM提供節點操作的方法是因為DOM節點關系指針都是只讀的

  下列代碼中想通過修改myUl的父級節點來修改其節點關系,但由於parentNode屬性是只讀的,所以修改無效,在IE8-瀏覽器下甚至會報錯

<div id="myDiv"></div>
<ul id="myUl">
    <li id="myli"></li>
</ul>
<script>
console.log(myUl.parentNode);//<body>
myUl.parentNode = myDiv;
//標準瀏覽器下,依然返回<body>;而IE8-瀏覽器則會報錯
console.log(myUl.parentNode);
</script>

  DOM節點操作方法包括創建節點、插入節點、刪除節點、替換節點、查看節點和復制節點。查看節點指的是查看節點之間的關系,在節點關系部分已經做過詳細介紹,就不再贅述

創建節點
createElement()

  document.createElement()方法可以創建新元素。這個方法接受一個參數,即要創建元素的標簽名,這個標簽名在HTML文檔中不區分大小寫

var oDiv = document.createElement("div");
console.log(oDiv);//<div>

  IE8-瀏覽器可以為這個方法傳入完整的元素標簽,也可以包含屬性

var oDiv = document.createElement(‘<div id="box"></div>‘);
console.log(oDiv.id);//‘box‘

  利用這種方法可以避開IE7-瀏覽器在動態創建元素的下列問題  

  1、不能設置動態創建的<iframe>元素的name特性

  2、不能通過表單的reset()方法重設動態創建的<input>元素

  3、動態創建的type特性值為"reset"的<button>元素重設不了表單

  4、動態創建的一批name相同的單選按鈕彼此毫無關系。name值相同的一組單選按鈕本來應該用於表示同一選項的不同值,但動態創建的一批這種單選按鈕之間卻沒有這種關系

var iframe = document.createElement("<iframe name = ‘myframe‘></iframe>");
var input = document.createElement("<input type=‘checkbox‘>);
var button = document.createElement("<button type = ‘reset‘></button>");
var radio1 = document.createElement("<input type=‘radio‘ name =‘choice‘ value = ‘1‘>");
var radio2 = document.createElement("<input type=‘radio‘ name =‘choice‘ value = ‘2‘>");

  所有節點都有一個ownerDocument的屬性,指向表示整個文檔的文檔節點document;在使用createElement()方法創建新元素的同時,也為新元素設置了ownerDocument屬性

<div id="myDiv"></div>
<script>
console.log(myDiv.ownerDocument);//document
var newDiv = document.createElement(‘div‘);
console.log(newDiv.ownerDocument);//document
console.log(newDiv.ownerDocument === myDiv.ownerDocument);//true
</script>

插入節點
appendChild()

  appendChild()方法用於向childNodes列表的末尾添加一個節點,並返回新增節點。添加節點後,childNodes中的新增節點、父節點和以前的最後一個子節點的關系指針都會相應地得到更新

<div id="box"></div>
<script>
var oBox = document.getElementById(‘box‘);
var newNode = document.createElement(‘ul‘);
var returnedNode = oBox.appendChild(newNode);
console.log(returnedNode.nodeName);//UL
console.log(returnedNode == newNode);//true
console.log(returnedNode == oBox.lastChild);//true
</script>

  如果插入的節點已經是文檔的一部分了,那結果就是將該節點從原來的位置轉移到新位置

<body>
<div id="oldDiv">第一個div</div>
<div id="newDiv">第二個div</div>
<button id="btn">變換位置</button>
<script>
btn.onclick = function(){
    document.body.appendChild(newDiv);
}    
</script>
</body>

insertBefore()

  insertBefore()方法接收兩個參數:要插入的節點和作為參照的節點。插入節點後,被插入的節點會變成參照節點的前一個兄弟節點(previousSibling),同時被方法返回。如果參照節點是null,則insertBefore()與appendChild()方法執行相同的操作。同樣地,如果插入的節點已經是文檔的一部分了,那結果就是將該節點從原來的位置轉移到新位置

referenceNode.parentNode.insertBefore(newNode,referenceNode);

<ul id="myUl" style="border:1px solid black;">
    <li id="myLi">
        <div id=‘oldDiv‘>oldDiv</div>
    </li>    
</ul>
<button id="btn1">插入oldDiv的前面</button>
<button id="btn2">插入myUl的前面</button>
<button id="btn3">插到oldDiv的裏面</button>
<script>
var oDiv = document.createElement(‘div‘);
oDiv.innerHTML = ‘newDiv‘;
btn1.onclick = function(){
    console.log(myLi.insertBefore(oDiv,oldDiv));//<div>newDiv</div>
}
btn2.onclick = function(){
    console.log(document.body.insertBefore(oDiv,myUl));//<div>newDiv</div>
}
btn3.onclick = function(){
    console.log(oldDiv.insertBefore(oDiv,null));//<div>newDiv</div>
}
</script>

【小效果】

<ul class="list" id="list">
    <li class="in">1</li>
    <li class="in">2</li>
    <li class="in">3</li>
    <li class="in">4</li>
    <li class="in">5</li>
    <li class="in">6</li>        
</ul>
<script>
var oList = document.getElementById(‘list‘);
//新增一個li元素
var oAdd = document.createElement(‘li‘);
//設置新增元素的css樣式
oAdd.className = "in";
oAdd.style.cssText = ‘background-color:red;border-radius:50%‘;
//添加到oList中
oList.insertBefore(oAdd,null);
var num = -1;
var max = oList.children.length;
function incrementNumber(){
    num++;
    //oList.getElementsByTagName(‘li‘)[max]相當於null,所以不報錯
    oList.insertBefore(oAdd,oList.getElementsByTagName(‘li‘)[num]);    
    if(num == max){
        num = -1;
    }    
    if(num == 0){
        num = 1;
    }
    setTimeout(incrementNumber,1000);
}
setTimeout(incrementNumber,1000);
</script>

insertAfter()

  由於不存在insertAfter()方法,如果要插在當前節點的某個子節點後面,可以用insertBefore()和appendChild()封裝方法

function insertAfter(newElement,targetElement){
    var parent = targetElement.parentNode;
    if(parent.lastChild == targetElement){
        parent.appendChild(newElement);
    }else{
        parent.insertBefore(newElement,targetElement.nextSibling)
    }
}
<div id=‘oldDiv‘>old</div>
<button id="btn">增加節點</button>
<script>
function insertAfter(newElement,targetElement){
    var parent = targetElement.parentNode;
    if(parent.lastChild == targetElement){
       return parent.appendChild(newElement);
    }else{
       return parent.insertBefore(newElement,targetElement.nextSibling)
    }
}    
var newDiv = document.createElement(‘div‘);
newDiv.innerHTML = ‘new‘;
btn.onclick = function(){
    insertAfter(newDiv,oldDiv);
}
</script>

insertAdjacentHTML()

  insertAdjacentHTML()方法作為終級辦法,相當於前面三個方法的綜合。該方法接收兩個參數:插入的位置和要插入的HTML文本

  第一個參數必須是下列值之一,且這些值都必須是小寫形式:

  "beforebegin"   在當前元素之前插入一個緊鄰的同級元素
  "afterbegin"   在當前元素之下插入一個新的子元素或在第一個子元素之前再插入新的子元素
  "beforeend" 在當前元素之下插入一個新的子元素或在最後一個子元素之後再插入新的子元素
  "afterend" 在當前元素之後插入一個緊鄰的同級元素
  第二個參數是一個HTML字符串,如果瀏覽器無法解析字符串,就會拋出錯誤

  [註意]該方法無返回值

<div id=‘target‘ style="border: 1px solid black;">This is the element content</div>
<button>beforebegin</button>
<button>afterbegin</button>
<button>beforeend</button>
<button>afterend</button>
<script>
var btns = document.getElementsByTagName(‘button‘);
for(var i = 0 ; i < 4; i++){
    btns[i].onclick = function(){
        var that = this;
        target.insertAdjacentHTML(that.innerHTML,‘<span id="test">測試</span>‘)    
    }
}
</script>    

移除節點
removeChild()

  removeChild()方法接收一個參數,即要移除的節點,被移除的節點成為方法的返回值

<div id="myDiv">等待移除的節點</div>
<button id="btn">移除節點</button>
<script>
btn.onclick = function(){
    document.body.removeChild(myDiv);
}
</script>

  

remove()

  相比於removeChild(),remove()方法不太常見,但是卻非常簡單。該方法不用調用其父節點,直接在當前節點使用remove()方法就可以刪除該節點,無返回值

  remove()方法常用於刪除元素節點和文本節點,不可用於特性節點

  [註意]IE瀏覽器不支持該方法

<div id="test" title=‘div‘>123</div>
<script>
//文本節點
console.log(test.childNodes[0]);//‘123‘
test.childNodes[0].remove();
console.log(test.childNodes[0]);//undefined

//特性節點
console.log(test.attributes.title);//‘div‘
//報錯,remove()方法無法用於刪除特性節點
try{test.attributes[0].remove()}catch(e){
    console.log(‘error‘);
}
//元素節點
console.log(test);
test.remove();
</script>

替換節點
replaceChild()

  replaceChild()接收的兩個參數是要插入的節點和要替換的節點,要替換的節點將由這個方法返回並從文檔樹中移除,同時由要插入的節點占據其位置

oldChild.parentNode.replaceChild(newChild, oldChild);

<div id="div1">1</div>
<div id="div2">2</div>
<div id="div3">3</div>
<button id="btn1">新增節點替換(4替換2)</button>
<button id="btn2">原有節點替換(3替換1)</button>
<script>
btn2.onclick = function(){
    document.body.replaceChild(div3,div1);
}
btn1.onclick = function(){
    var div4 = document.createElement(‘div‘);
    div4.innerHTML = ‘4‘;
    document.body.replaceChild(div4,div2);
}
</script>

復制節點
cloneNode()

  cloneNode方法用於克隆一個節點。它接受一個布爾值作為參數,表示是否執行深復制。在參數為true時,執行深復制,也就是復制節點及整個子節點樹。在參數為false的情況下,執行淺復制,即復制節點本身。復制後返回的節點副本屬於文檔所有,但並沒有為它指定父節點。若參數為空,也相當於false 

  [註意]cloneNode()方法不會復制添加到DOM節點中的javascript屬性,例如事件處理程序等。這個方法只復制特性和子節點,其他一切都不會復制

<ul id="list">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>        
</ul>
<script>
var oList = document.getElementById(‘list‘);
oList.index = 0;

var deepList = oList.cloneNode(true);
//成功復制了子節點
console.log(deepList.children.length);//6
//但並沒有復制屬性
console.log(deepList.index);//undefined
var shallowList = oList.cloneNode();
//淺復制不復制子節點
console.log(shallowList.children.length);//0
</script>

javascript學習筆記:DOM節點關系和操作