操作DOM的核心就是增刪改查
參考:
目錄
一、節(jié)點創(chuàng)建型API
- 1.1 createElement
- 1.2 createTextNode
- 1.3 cloneNode
- 1.4 createDocumentFragment
二、頁面修改形API(包括刪除和添加)(刪)(改)
- 2.1 appendChild(追加為子元素)
- 2.2 insertBefore(插入前面)
- 2.3 removeChild(刪除子元素)
- 2.4 replaceChild(替換子元素)
三 節(jié)點查詢型API(查)
- 3.1 document.getElementById
- 3.2 document.getElementsByTagName
- 3.3 document.getElementsByName
- 3.4 document.getElementsByClassName
- 3.5 document.querySelector和document.querySelectorAll
- 3.6 elementFromPoint()
四 元素屬性型操作(屬性節(jié)點的操作)
一般只操作html標簽上的屬性如id;class之類的;
但是style屬性如寬高背景色之類的一般寫在css里面所以要通過element.currentStyle : window.getComputedStyle(element,pseduoElement)
來操作詳細請看BOM筆記
- 4.1 getAttribute() (獲取屬性)
- 4.2 createAttribute() (創(chuàng)建屬性)
- 4.3 setAttribute() (設(shè)置屬性)
- 4.4 romoveAttribute() (刪除屬性)
- 4.5 element.attributes(將屬性生成數(shù)組對象)
五 innerText和innerHTML(outerHTML)有什么區(qū)別?
DOM基本操作思維導(dǎo)圖
一、節(jié)點創(chuàng)建型API(增)
在這里,我將常用的DOM操作api進行分類,首先要介紹的是創(chuàng)建型的api。這一類型的api,簡而言之就是用來創(chuàng)建節(jié)點的
1.1 createElement
createElement通過傳入指定的一個標簽名來創(chuàng)建一個元素,如果傳入的標簽名是一個未知的,則會創(chuàng)建一個自定義的標簽,注意:IE8以下瀏覽器不支持自定義標簽
var div = document.createElement("div");
使用createElement要注意:通過createElement創(chuàng)建的元素并不屬于html文檔,它只是創(chuàng)建出來,并未添加到html文檔中,要調(diào)用appendChild或insertBefore等方法將其添加到HTML文檔樹中;
1.2 createTextNode
createTextNode用來創(chuàng)建一個文本節(jié)點,用法如下
var textNode = document.createTextNode("一個TextNode");
createTextNode接收一個參數(shù),這個參數(shù)就是文本節(jié)點中的文本,和createElement一樣,創(chuàng)建后的文本節(jié)點也只是獨立的一個節(jié)點,同樣需要append Child將其添加到HTML文檔樹中
1.3 cloneNode
cloneNode是用來返回調(diào)用方法的節(jié)點的一個副本,它接收一個bool參數(shù),用來表示是否復(fù)制子元素,使用如下:
var parent = document.getElementById("parentElement");
var parent2 = parent.cloneNode(true);// 傳入true
parent2.id = "parent2";
這段代碼通過cloneNode復(fù)制了一份parent元素,其中cloneNode的參數(shù)為true,表示parent的子節(jié)點也被復(fù)制,如果傳入false,則表示只復(fù)制了parent節(jié)點
<body>
<div id="parent">
我是父元素的文本
<br/>
<span>
我是子元素
</span>
</div>
<button id="btnCopy">復(fù)制</button>
<script>
var parent = document.getElementById("parent");
//點擊id="btnCopy"點擊執(zhí)行執(zhí)行函數(shù)(函數(shù)內(nèi)是一個元素的拷貝)
document.getElementById("btnCopy").onclick = function(){
var parent2 = parent.cloneNode(true);
parent2.id = "parent2";
document.body.appendChild(parent2);
}
</script>
</body>
這段代碼很簡單,主要是綁定button事件,事件內(nèi)容是復(fù)制了一個parent,修改其id,然后添加到文檔中
這里有幾點要注意:
和createElement一樣,cloneNode創(chuàng)建的節(jié)點只是游離有html文檔外的節(jié)點,要調(diào)用appendChild方法才能添加到文檔樹中
如果復(fù)制的元素有id,則其副本同樣會包含該id,由于id具有唯一性,所以在復(fù)制節(jié)點后必須要修改其id
調(diào)用接收的bool參數(shù)最好傳入,如果不傳入該參數(shù),不同瀏覽器對其默認值的處理可能不同
除此之外,我們還有一個需要注意的點:
如果被復(fù)制的節(jié)點綁定了事件,則副本也會跟著綁定該事件嗎?這里要分情況討論:
如果是通過addEventListener或者比如onclick進行綁定事件,則副本節(jié)點不會綁定該事件
如果是內(nèi)聯(lián)方式綁定比如
<div onclick="showParent()"></div>
這樣的話,副本節(jié)點同樣會觸發(fā)事件
1.4 createDocumentFragment
createDocumentFragment方法用來創(chuàng)建一個DocumentFragment。在前面我們說到DocumentFragment表示一種輕量級的文檔,它的作用主要是存儲臨時的節(jié)點用來準備添加到文檔中
createDocumentFragment方法主要是用于添加大量節(jié)點到文檔中時會使用到。假設(shè)要循環(huán)一組數(shù)據(jù),然后創(chuàng)建多個節(jié)點添加到文檔中
<ul id="list"></ul>
<input type="button" value="添加多項" id="btnAdd" />
document.getElementById("btnAdd").onclick = function(){
var list = document.getElementById("list");
for(var i = 0;i < 100; i++){
var li = document.createElement("li");
li.textContent = i;
list.appendChild(li);
}
}
這段代碼將按鈕綁定了一個事件,這個事件創(chuàng)建了100個li節(jié)點,然后依次將其添加HTML文檔中。這樣做有一個缺點:每次一創(chuàng)建一個新的元素,然后添加到文檔樹中,這個過程會造成瀏覽器的回流。所謂回流簡單說就是指元素大小和位置會被重新計算,如果添加的元素太多,會造成性能問題。
這個時候,就是使用createDocumentFragment了DocumentFragment不是文檔樹的一部分,它是保存在內(nèi)存中的,所以不會造成回流問題。我們修改上面的代碼如下
document.getElementById("btnAdd").onclick = function(){
var list = document.getElementById("list");
var fragment = document.createDocumentFragment();
for(var i = 0;i < 100; i++){
var li = document.createElement("li");
li.textContent = i;
fragment.appendChild(li);
}
list.appendChild(fragment);
}
優(yōu)化后的代碼主要是創(chuàng)建了一個fragment,每次生成的li節(jié)點先添加到fragment,最后一次性添加到list
1.5 創(chuàng)建型API總結(jié)
創(chuàng)建型api主要包括
- createElement
- createTextNode
- cloneNode
- createDocumentFragment
四個方法.
需要注意下面幾點:
- 它們創(chuàng)建的節(jié)點只是一個孤立的節(jié)點,
- 要通過appendChild添加到文檔中
- cloneNode要注意如果被復(fù)制的節(jié)點是否包含子節(jié)點以及事件綁定等問題
- 使用createDocumentFragment來解決添加大量節(jié)點時的性能問題
二、頁面修改形API(包括刪除和添加)(刪)(改)
前面我們提到創(chuàng)建型api,它們只是創(chuàng)建節(jié)點,并沒有真正修改到頁面內(nèi)容,而是要調(diào)用appendChild來將其添加到文檔樹中。我在這里將這類會修改到頁面內(nèi)容歸為一類。
修改頁面內(nèi)容的api主要包括:
- appendChild(追加為子元素)
- insertBefore(插入前面)
- removeChild(刪除子元素)
- replaceChild(替換子元素)
2.1 appendChild
appendChild我們在前面已經(jīng)用到多次,就是將指定的節(jié)點添加到調(diào)用該方法的節(jié)點的子元素的末尾。調(diào)用方法如下:
parent.appendChild(child);
child節(jié)點將會作為parent節(jié)點的最后一個子節(jié)點
appendChild這個方法很簡單,但是還有有一點需要注意:如果被添加的節(jié)點是一個頁面中存在的節(jié)點,則執(zhí)行后這個節(jié)點將會添加到指定位置,其原本所在的位置將移除該節(jié)點,也就是說不會同時存在兩個該節(jié)點在頁面上,相當(dāng)于把這個節(jié)點移動到另一個地方
<div id="child">
要被添加的節(jié)點
</div>
<br/>
<br/>
<br/>
<div id="parent">
要移動的位置
</div>
<input id="btnMove" type="button" value="移動節(jié)點" />
<script>
document.getElementById("btnMove").onclick = function(){
var child = document.getElementById("child");
document.getElementById("parent").appendChild(child);
}
</script>
這段代碼主要是獲取頁面上的child節(jié)點,然后添加到指定位置,可以看到原本的child節(jié)點被移動到parent中了。
這里還有一個要注意的點:如果child綁定了事件,被移動時,它依然綁定著該事件
2.2 insertBefore
insertBefore用來添加一個節(jié)點到一個參照節(jié)點之前,用法如下
parentNode.insertBefore(newNode,refNode);
parentNode表示新節(jié)點被添加后的父節(jié)點
newNode表示要添加的節(jié)點
refNode表示參照節(jié)點,新節(jié)點會添加到這個節(jié)點之前
<div id="parent">
父節(jié)點
<div id="child">子元素</div>
</div>
<input type="button" id="insertNode" value="插入節(jié)點" />
<script>
var parent = document.getElementById("parent");
var child = document.getElementById("child");
document.getElementById("insertNode").onclick = function(){
var newNode = document.createElement("div");
newNode.textContent = "新節(jié)點"
parent.insertBefore(newNode,child);
}
</script>
這段代碼創(chuàng)建了一個新節(jié)點,然后添加到child節(jié)點之前
和appendChild一樣,如果插入的節(jié)點是頁面上的節(jié)點,則會移動該節(jié)點到指定位置,并且保留其綁定的事件。
關(guān)于第二個參數(shù)參照節(jié)點還有幾個注意的地方:
-
refNode
是必傳的,如果不傳該參數(shù)會報錯 - 如果refNode是undefined或null,則insertBefore會將節(jié)點添加到子元素的末尾
2.3 removeChild
let oldChild = node.removeChild(child);
//OR
element.removeChild(child);
- child 是要移除的那個子節(jié)點.
- node 是child的父節(jié)點.
- oldChild保存對刪除的子節(jié)點的引用. oldChild === child.
被移除的這個子節(jié)點仍然存在于內(nèi)存中,只是沒有添加到當(dāng)前文檔的DOM樹中,因此,你還可以把這個節(jié)點重新添加回文檔中,當(dāng)然,實現(xiàn)要用另外一個變量比如上例中的oldChild來保存這個節(jié)點的引用. 如果使用上述語法中的第二種方法, 即沒有使用 oldChild 來保存對這個節(jié)點的引用, 則認為被移除的節(jié)點已經(jīng)是無用的,在短時間內(nèi)將會被內(nèi)存管理回收.
如果上例中的child節(jié)點不是node節(jié)點的子節(jié)點,則該方法會拋出異常.
// 先定位父節(jié)點,然后刪除其子節(jié)點
var d = document.getElementById("top");
var d_nested = document.getElementById("nested");
var throwawayNode = d.removeChild(d_nested);
// 無須定位父節(jié)點,通過parentNode屬性直接刪除自身
var node = document.getElementById("nested");
if (node.parentNode) {
node.parentNode.removeChild(node);
}
// 移除一個元素節(jié)點的所有子節(jié)點
var element = document.getElementById("top");
while (element.firstChild) {
element.removeChild(element.firstChild);
}
2.4 replaceChild
replaceChild用于使用一個節(jié)點替換另一個節(jié)點,用法如下
parent.replaceChild(newChild,oldChild);
newChild是替換的節(jié)點,可以是新的節(jié)點,也可以是頁面上的節(jié)點,如果是頁面上的節(jié)點,則其將被轉(zhuǎn)移到新的位置
oldChild是被替換的節(jié)點
2.5 頁面修改型API總結(jié)
頁面修改型api主要是這四個接口,要注意幾個特點:
不管是新增還是替換節(jié)點,如果新增或替換的節(jié)點是原本存在頁面上的,則其原來位置的節(jié)點將被移除,也就是說同一個節(jié)點不能存在于頁面的多個位置
節(jié)點本身綁定的事件會不會消失,會一直保留著
三 節(jié)點查詢型API(查)
節(jié)點查詢型API也是非常常用的
3.1 document.getElementById
這個接口很簡單,根據(jù)元素id返回元素,返回值是Element類型,如果不存在該元素,則返回null
使用這個接口有幾點要注意:
- 元素的Id是大小寫敏感的,一定要寫對元素的id
- HTML文檔中可能存在多個id相同的元素,則返回第一個元素
- 只從文檔中進行搜索元素,如果創(chuàng)建了一個元素并指定id,但并沒有添加到文檔中,則這個元素是不會被查找到的
3.2 document.getElementsByTagName
這個接口根據(jù)元素標簽名獲取元素,返回一個即時的HTMLCollection類型,什么是即時的HTMLCollection類型呢?
<div>div1</div>
<div>div2</div>
<input type="button" value="顯示數(shù)量" id="btnShowCount"/>
<input type="button" value="新增div" id="btnAddDiv"/>
<script>
var divList = document.getElementsByTagName("div");
document.getElementById("btnAddDiv").onclick = function(){
var div = document.createElement("div");
div.textContent ="div" + (divList.length+1);
document.body.appendChild(div);
}
document.getElementById("btnShowCount").onclick = function(){
alert(divList.length);
}
</script>
這段代碼中有兩個按鈕,一個按鈕是顯示HTMLCollection元素的個數(shù),另一個按鈕可以新增一個div標簽到文檔中。前面提到HTMLCollcetion元素是即時的表示該集合是隨時變化的,
可以看出其實document.getElementsByTagName返回的是一個數(shù)組對象我們可以用數(shù)組的方式對其進行操作。
使用document.getElementsByTagName
這個方法有幾點要注意:
- 如果要對HTMLCollection集合進行循環(huán)操作,最好將其長度緩存起來,因為每次循環(huán)都會去計算長度,暫時緩存起來可以提高效率
- 如果沒有存在指定的標簽,該接口返回的不是null,而是一個空的HTMLCollection
- 參數(shù)“*”表示所有標簽
3.3 document.getElementsByName
getElementsByName
主要是通過指定的name屬性來獲取元素,它返回一個即時的NodeList(節(jié)點列表)對象。一般用于獲取表單元素的·name·屬性
使用這個接口主要要注意幾點:
- 返回對象是一個即時的NodeList,它是隨時變化的
- 在HTML元素中,并不是所有元素都有name屬性,比如div是沒有name屬性的,但是如果強制設(shè)置div的name`屬性,它也是可以被查找到的
- 在IE中,如果id設(shè)置成某個值,然后傳入getElementsByName的參數(shù)值和id值一樣,則這個元素是會被找到的,所以最好不好設(shè)置同樣的值給id和name
3.4 document.getElementsByClassName
這個API是根據(jù)元素的class返回一個即時的HTMLCollection,用法如下
var elements = document.getElementsByClassName(names);
這個接口有下面幾點要注意:
- 返回結(jié)果是一個即時的HTMLCollection,會隨時根據(jù)文檔結(jié)構(gòu)變化
- IE9以下瀏覽器不支持
- 如果要獲取2個以上classname,可傳入多個classname,每個用空格相隔,例如
var elements = document.getElementsByClassName("test1 test2")
;
3.5 document.querySelector和document.querySelectorAll
這兩個api很相似,通過css選擇器來查找元素,注意選擇器要符合CSS選擇器的規(guī)則
document.querySelector
querySelector方法返回匹配指定的CSS選擇器的元素節(jié)點。如果有多個節(jié)點滿足匹配條件,則返回第一個匹配的節(jié)點。如果沒有發(fā)現(xiàn)匹配的節(jié)點,則返回null。
var el1 = document.querySelector(".myclass");
var el2 = document.querySelector('#myParent > [ng-click]');
querySelector方法無法選中CSS偽元素。
注意,由于返回的是第一個匹配的元素,這個api使用的深度優(yōu)先搜索來獲取元素
<div>
<div>
<span class="test">第三級的span</span>
</div>
</div>
<div class="test">
同級的第二個div
</div>
<input type="button" id="btnGet" value="獲取test元素" />
<script>
document.getElementById("btnGet").addEventListener("click",function(){
var element = document.querySelector(".test");
alert(element.textContent);
})
</script>
這個例子很簡單,就是兩個class都包含“test”的元素,一個在文檔樹的前面,但是它在第三級,另一個在文檔樹的后面,但它在第一級,通過querySelector獲取元素時,它通過深度優(yōu)先搜索,拿到文檔樹前面的第三級的元素(文檔樹前面優(yōu)先深度優(yōu)先)
document.querySelectorAll
document.querySelectorAll的不同之處在于它返回的是所有匹配的元素,querySelectorAll方法的參數(shù),可以是逗號分隔的多個CSS選擇器,返回所有匹配其中一個選擇器的元素。
var matches = document.querySelectorAll("div.note, div.alert");
例子
<div class="test">
class為test
</div>
<div id="test">
id為test
</div>
<input id="btnShow" type="button" value="顯示內(nèi)容" />
<script>
document.getElementById("btnShow").addEventListener("click",function(){
var elements = document.querySelectorAll("#test,.test");
for(var i = 0,length = elements.length;i<length;i++){
alert(elements[i].textContent);
}
})
</script>
這段代碼通過querySelectorAll,使用id選擇器和class選擇器選擇了兩個元素,并依次輸出其內(nèi)容。要注意兩點:
- querySelectorAll也是通過深度優(yōu)先搜索,搜索的元素順序和選擇器的順序無關(guān)
- 返回的是一個非即時的NodeList,也就是說結(jié)果不會隨著文檔樹的變化而變化
兼容性問題:querySelector和querySelectorAll在ie8以下的瀏覽器不支持
3.6 elementFromPoint()
elementFromPoint方法返回位于頁面指定位置的元素。
var element = document.elementFromPoint(x, y);
上面代碼中,elementFromPoint方法的參數(shù)x和y,分別是相對于當(dāng)前窗口左上角的橫坐標和縱坐標,單位是CSS像素。
elementFromPoint方法返回位于這個位置的DOM元素,如果該元素不可返回(比如文本框的滾動條),則返回它的父元素(比如文本框)。如果坐標值無意義(比如負值),則返回null。
小結(jié):
- document.getElementById返回一個對象
- document.getElementsByName和document.getElementsByClasName返回一個對象數(shù)組
四 元素屬性型操作(屬性節(jié)點的操作)
4.1 getAttribute()(獲取屬性)
getAttribute()用于獲取元素的attribute值
node.getAttribute('id');
//表示獲取node元素的id屬性的 ‘值’
4.2 createAttribute()(創(chuàng)建屬性)
createAttribute()
方法生成一個新的屬性對象節(jié)點,并返回它。
attribute = document.createAttribute(name);
createAttribute方法的參數(shù)name,是屬性的名稱。
4.3 setAttribute()(設(shè)置屬性)
setAttribute()方法用于設(shè)置元素屬性
var node = document.getElementById("div1");
node.setAttribute(name, value);
//name為屬性名稱 ;value為屬性值
例如
var node = document.getElementById("div1");
node.setAttribute("id", "ct");
等同于
var node = document.getElementById("div1");
var a = document.createAttribute("id");
a.value = "ct";
node.setAttributeNode(a);
4.4 romoveAttribute()(刪除屬性)
removeAttribute()用于刪除元素屬性
node.removeAttribute('id');
4.5 element.attributes(將屬性生成數(shù)組對象)
當(dāng)然上面的方法做的事情也可以通過類似操作數(shù)組屬性element.attributes來實現(xiàn)
語法
var attr = element.attributes;
示例
// 獲取文檔的第一個 <p> 元素
var para = document.getElementsByTagName("p")[0];
//獲取該元素屬性(多個屬性會形成一個數(shù)組對象)
var atts = para.attributes;
遍歷元素的屬性
索引有利于遍歷一個元素的所有屬性。
在以下例子中會遍歷文檔中 id 為 "paragraph" 元素的屬性節(jié)點,并打印出來。
<p id="paragraph" style="color: green;">Sample Paragraph</p>
<form action="">
<p>
<input type="button" value="顯示第一個屬性及其值" onclick="showFirstAttr();">
<input id="result" type="text" value="">
</p>
</form>
<script type="text/javascript">
function showFirstAttr() {
var paragraph= document.getElementById("paragraph");
var result= document.getElementById("result");
// 首先,讓我們確認這個段落有一些屬性。
//hasAttributes屬性返回一個布爾值true或false,來表明當(dāng)前元素節(jié)點是否有至少一個的屬性(attribute).
if (paragraph.hasAttributes()) {
var attrs = paragraph.attributes;
var output= "";
for(var i=attrs.length-1; i>=0; i--) {
output+= attrs[i].name + "->" + attrs[i].value;
}
result.value = output; //寫入 value=""使文本框出現(xiàn)內(nèi)容
} else {
result.value = "沒有屬性顯示了" //寫入 value="沒有屬性顯示了"使文本框出現(xiàn)內(nèi)容
}
}
</script>
五 innerText和innerHTML(outerHTML)有什么區(qū)別?
innerText
innerText是一個可寫屬性,返回元素內(nèi)包含的文本內(nèi)容,在多層次的時候會按照元素由淺到深的順序拼接其內(nèi)容
<div id="test">
<!--注釋-->
<p>我的名字叫<span>哈哈哈</span></p>
</div>
<script>
var test = document.getElementById('test')
console.log(test.innerText);
</script>
返回的結(jié)果
‘我的名字叫哈哈哈’
只包含文本節(jié)點
innerHTML、outerHTML
innerHTML屬性作用和innerText類似,但是不是返回元素的文本內(nèi)容,而是返回元素的HTML結(jié)構(gòu),在寫入的時候也會自動構(gòu)建DOM
<div id="test">
<!--注釋-->
<p>我的名字叫<span>哈哈哈</span></p>
</div>
<script>
var test = document.getElementById('test')
console.log(test.innerHTML);
</script>
返回的結(jié)果
<!--注釋-->
<p>我的名字叫<span>哈哈哈</span></p>
包含整個html結(jié)構(gòu);(元素節(jié)點、注釋節(jié)點、文本節(jié)點)
<div id="test">
<!--注釋-->
<p>我的名字叫<span>哈哈哈</span></p>
</div>
<script>
var test = document.getElementById('test')
console.log(test.outerHTML);
</script>
返回的結(jié)果
<div id="test">
<p>我的名字叫<span>哈哈哈</span></p>
</div>
包括自身和包含整個html結(jié)構(gòu);(元素節(jié)點、注釋節(jié)點、文本節(jié)點)
區(qū)別:
- innerText返回的是元素內(nèi)包含的文本內(nèi)容(只返回文本節(jié)點類型);
- innerHTML返會元素內(nèi)HTML結(jié)構(gòu),包括元素節(jié)點、注釋節(jié)點、文本節(jié)點;
- outerHTML返回包括元素節(jié)點自身和里面的所有元素節(jié)點、注釋節(jié)點、文本節(jié)點;