通過上一節(jié)的學(xué)習(xí),對(duì)JavaScript中的DOM有了一定的認(rèn)識(shí),。雖然對(duì)DOM中相關(guān)的知識(shí)點(diǎn)有一定的概念,但還是缺乏對(duì)DOM的實(shí)際操作,。如果你仔細(xì)閱讀過上一篇文章的話,你應(yīng)該會(huì)發(fā)現(xiàn),當(dāng)時(shí)也提到了一些DOM操作相關(guān)的東西,,比如,,DOM的增,、刪、改和查等,。那麼今天我們就來看看這些方面的東西,。
DOM的增
先來看DOM操作中的增。其主要分為兩個(gè)部分:新創(chuàng)建節(jié)點(diǎn)和插入節(jié)點(diǎn),。
新創(chuàng)建節(jié)點(diǎn)
常用的DOM節(jié)點(diǎn)創(chuàng)建有關(guān)的API接口主要有:
document.createElement :創(chuàng)建指定的HTML元素或一個(gè)HTMLUnknownElement
document.createTextNode :創(chuàng)建文本節(jié)點(diǎn)
document.createDocumentFrame :創(chuàng)建文檔片段
document.createAttribute :創(chuàng)建節(jié)點(diǎn)屬性
document.adoptNode :從外部文檔中獲取一個(gè)節(jié)點(diǎn)
document.importNode :拷貝外部文檔的一個(gè)節(jié)點(diǎn)
node.cloneNode :克隆節(jié)點(diǎn)
document.createElement
document.createElement(tagName[, options]) 是其中最常用的DOM API之一,,主要用來創(chuàng)建由標(biāo)籤名稱(tagName )指定的HTML元素,如果標(biāo)籤名稱不是一個(gè)有效的HTML元素,,將會(huì)創(chuàng)建一個(gè)HTMLUnknownElement 對(duì)象,。來看一個(gè)簡(jiǎn)單的示例:
let newEle = document.createElement('div');
let newContent = document.createTextNode('我是一個(gè)新創(chuàng)建的div元素')
newEle.appendChild(newContent)
document.body.appendChild(newEle)
注意,通過document.createElement 創(chuàng)建的元素並不屬於document 對(duì)象,,它只是創(chuàng)建出來,,並未添加到HTML文檔中,需要調(diào)用appendChild() 或insertBefore() 等方法將其添加到HTML文檔中,。
如果你對(duì)HTMLUnknownElement 從未接觸,,建議你有空花點(diǎn)時(shí)間閱讀@張?chǎng)涡窭蠞竦摹?a href="http://www./wordpress/2018/03/htmlunknownelement-html5-custom-elements/" target="_blank">》和@米粽大大翻譯的《Custom Elements》。
document.createTextNode
document.createTextNode(text) 創(chuàng)建一個(gè)文本節(jié)點(diǎn),,參數(shù)text 為文本節(jié)點(diǎn)的內(nèi)容,。比如:
let newContent = document.createTextNode('我是一個(gè)新創(chuàng)建的div元素')
newEle.appendChild(newContent)
創(chuàng)建了一個(gè)文本節(jié)點(diǎn)newContent ,然後把這個(gè)新創(chuàng)建的文本節(jié)點(diǎn)通過appendChild() 方法,,將其插入到newEle 元素中,,當(dāng)作其內(nèi)容。document.createTextNode(text) 方法返回的節(jié)點(diǎn),被瀏覽器當(dāng)作文本渲染,,而不是當(dāng)作HTML代碼渲染,,因此會(huì)對(duì)HTML代碼進(jìn)行轉(zhuǎn)義,可以用來展示用戶的輸入,,避免XSS 攻擊,。
function escapeUserInput(str) {
var div = document.createElement('div');
div.appendChild(document.createTextNode(str));
return div.innerHTML;
}
var userInput = '<p>危險(xiǎn)內(nèi)容</p>';
var template = '<div>' + escapeUserInput(userInput) + '</div>'
// 此時(shí)被轉(zhuǎn)義,危險(xiǎn)內(nèi)容不再危險(xiǎn)
<div><p>危險(xiǎn)內(nèi)容</p></div>
但是,,該方法不對(duì)單引號(hào)和雙引號(hào)轉(zhuǎn)義,,因此用來為屬性賦值的時(shí)候,仍然會(huì)被 XSS 攻擊:
var userInput = '" onmouseover="console.log(\'危險(xiǎn)操作\')" "';
var template = '<div color="' + escapeUserInput(userInput) + '">user set color</div>'
// 被注入一個(gè) onmouseover 操作
<div color="" onmouseover="console.log('危險(xiǎn)操作')" "">user set color</div>
其中XSS 攻擊屬於Web安全方面的知識(shí)了,,不屬於這篇文章的範(fàn)疇,。如果你對(duì)XSS 相關(guān)的東西感興趣的話,可以看看下面幾篇文章:
document.createDocumentFragment
document.createDocumentFragment() 方法創(chuàng)建一個(gè)新空白的DocumentFragment 對(duì)象,。
DocumentFragments 是DOM節(jié)點(diǎn),。它們不是主DOM樹的一部分。通常的用例是創(chuàng)建文檔片段,,將元素附加到文檔片段,,然後將文檔片段附加到DOM樹,。在DOM樹中,,文檔片段被其所有的子元素代替。
因?yàn)槲臋n片段存在於內(nèi)存中,,並不在DOM樹中,,所以將子元素插入到文檔片段時(shí)不會(huì)引起頁(yè)面回流(reflow )(對(duì)元素位置和幾何上的計(jì)算)。因此,,使用文檔片段document fragments 通常會(huì)起到優(yōu)化性能的作用,。
比如下面這個(gè)示例,給一個(gè)ul 添加10000 個(gè)li ,,先用拼接字符串的方式來實(shí)現(xiàn):
let start = Date.now()
let str = ''
let newUlEle = document.createElement('ul')
document.body.appendChild(newUlEle)
for (let i = 0; i < 10000; i++) {
str += '<li>第' + i + '個(gè)子節(jié)點(diǎn)</li>'
}
newUlEle.innerHTML = str
console.log('耗時(shí)' + (Date.now() - start) + 'ms');
多次刷新,,可以看到創(chuàng)建10000 個(gè)li 時(shí),渲染所需要的時(shí)間如下圖:
把上面的示例,,換成append() 的方式,,逐個(gè)添加對(duì)應(yīng)的li :
let start = Date.now()
let str = ''
let newUlEle = document.createElement('ul')
document.body.appendChild(newUlEle)
for (let i = 0; i < 10000; i++) {
let liEle = document.createElement('li')
liEle.textContent = '第' + i + '個(gè)子節(jié)點(diǎn)'
newUlEle.appendChild(liEle)
}
console.log('耗時(shí):' + (Date.now() - start) + 'ms')
這種方法所費(fèi)時(shí)間如下圖:
都說第二種方法要比第一種方法耗時(shí),看上去有點(diǎn)像,。接下來再來看createDocumentFragment 的方法,。可以預(yù)見的是,,這種方法肯定比第二種強(qiáng),,但應(yīng)該沒有第一種快:
let start = Date.now()
let str = ''
let newUlEle = document.createElement('ul')
document.body.appendChild(newUlEle)
let fragment = document.createDocumentFragment()
for (let i = 0; i < 10000; i++) {
let liEle = document.createElement('li')
liEle.textContent = '第' + i + '個(gè)子節(jié)點(diǎn)'
fragment.appendChild(liEle)
}
newUlEle.appendChild(fragment)
console.log('耗時(shí):' + (Date.now() - start) + 'ms')
document.createAttribute()
document.createAttribute(attrName) 方法創(chuàng)建並返回一個(gè)新的屬性節(jié)點(diǎn)。這個(gè)方法不是很常用,因?yàn)樘砑訉傩酝ǔJ褂?code>node.setAttribute(),。
let node = document.getElementById('content')
let attr = document.createAttribute('title')
attr.nodeValue = 'Hello JavaScript!'
node.setAttributeNode(attr)
上面的代碼會(huì)給div#content 的元素添加一個(gè)title 屬性,,而且這個(gè)title 屬性的值為Hello JavaScript! :
同樣的,document.createAttribute() 雖然創(chuàng)建了屬性節(jié)點(diǎn),,如果不通過setAttributeNode() 方法的話,,創(chuàng)建的屬性的節(jié)點(diǎn)是不會(huì)運(yùn)用到對(duì)應(yīng)的元素節(jié)點(diǎn)上的。該方法的返回值是一個(gè)Attr 類型的節(jié)點(diǎn),。借助nodeValue 給該節(jié)點(diǎn)賦值,,然後給該屬性節(jié)點(diǎn)設(shè)置對(duì)應(yīng)的屬性值。等同的效果,,常常使用setAttribute() 方法來替代該方法,。後續(xù)我們會(huì)介紹到setAttribute() 方法相關(guān)的知識(shí)。
document.adoptNode
document.adoptNode(externalNode) 從其他的 document 中獲取一個(gè)節(jié)點(diǎn)(externalNode ),,並將該節(jié)點(diǎn)以及它的所有子節(jié)點(diǎn)從原文檔刪除, 並且它的 ownerDocument 屬性會(huì)變成當(dāng)前的 document ,。之後你可以把這個(gè)節(jié)點(diǎn)插入到當(dāng)前文檔中,不常用,,瞭解即可,。
// 該函數(shù)用來從本文檔的第一個(gè) iframe 中獲取第一個(gè) element 元素,
// 並插入到當(dāng)前文檔樹中
function getEle(){
var iframe = document.getElementsByTagName("iframe")[0],
ele = iframe.contentWindow.document.body.firstElementChild;
if(ele){
document.body.appendChild(document.adoptNode(ele))
}else{
alert("沒有更多元素了")
}
}
document.getElementById("move").onclick = getEle
注意,,該方法在同一 document 下的不同兩個(gè)元素中也可以使用,,可以實(shí)現(xiàn)從左邊欄列表中選取某些元素加載到右邊欄的功能。如果節(jié)點(diǎn)資源來自不同的源的時(shí)候,,調(diào)用 adoptNode 可能會(huì)失敗,。
有些情況下,將外部文檔的節(jié)點(diǎn)插入當(dāng)前文檔之前,你需要使用 document.importNode() 從外部文檔導(dǎo)入源節(jié)點(diǎn),,瞭解更多細(xì)節(jié),。
document.importNode
document.importNode(externalNode, deep) 這個(gè)接口也不常用,作用是拷貝外部文檔的一個(gè)節(jié)點(diǎn)(externalNode ),。deep 表明是否要導(dǎo)入節(jié)點(diǎn)的後代節(jié)點(diǎn),,默認(rèn)為 false 不導(dǎo)入後代節(jié)點(diǎn)。
var iframe = document.getElementsByTagName("iframe")[0];
var oldNode = iframe.contentDocument.getElementById("myNode");
var newNode = document.importNode(oldNode, true);
document.getElementById("container").appendChild(newNode);
注意,,這個(gè)方法僅拷貝節(jié)點(diǎn),,此時(shí),節(jié)點(diǎn)存在於內(nèi)存中,,還需要插入當(dāng)前文檔中才能顯示,。
node.cloneNode
node.cloneNode(deep) 方法返回該節(jié)點(diǎn)的一個(gè)副本,deep 可選,,表明是否採(cǎi)用深度克隆,,如果為true ,,則該節(jié)點(diǎn)的所有後代節(jié)點(diǎn)也都會(huì)被克隆,否則,,只克隆該節(jié)點(diǎn)本身,。
let node = document.getElementById('content')
let cloneNode = node.cloneNode(true)
cloneNode.id = "newId"
document.body.appendChild(cloneNode)
上面的這個(gè)小示例,克隆了div#content 以及其所有後代節(jié)點(diǎn),,並且把新克隆的元素的id 賦值為newId ,,然後再把新克隆出來的所有節(jié)點(diǎn)重新插入body 中。最終的效果如下:
上面的示例,,演示了,,使用node.cloneNode(true) 可以克隆節(jié)點(diǎn)的所有後代節(jié)點(diǎn)以及其所有屬性。那麼對(duì)於綁定的事件是否也能被克隆呢,?還是通過示例來驗(yàn)證一下,,看看事件是否也會(huì)被克隆。
<div id="box">
<button id="clone" onclick="console.log('Click Clone Button')">Clone Me!</button>
</div>
<div id="new"></div>
// cloneNode
let btn = document.getElementById('clone')
let box = document.getElementById('box')
let newDiv = document.getElementById('new')
newDiv.appendChild(box.cloneNode(true))
上面的示例使用了內(nèi)聯(lián)方式直接把事件寫在HTML標(biāo)籤上,。從結(jié)果我們可以看到綁定在HTML標(biāo)籤上的事件也被克隆了,。
接下來在上例的基礎(chǔ)上做一下調(diào)整,把內(nèi)聯(lián)方式換成綁定在節(jié)點(diǎn)對(duì)象上的事件:
let btn = document.getElementById('clone')
let box = document.getElementById('box')
let newDiv = document.getElementById('new')
btn.onclick = function () {
console.log('click clone')
}
newDiv.appendChild(box.cloneNode(true))
從結(jié)果可以看出,,綁定在節(jié)點(diǎn)對(duì)象的事件在克隆的副本並不包含事件處理程序,。接著再做一下調(diào)整,使用addEventListener() 方法把事件添加在節(jié)點(diǎn)上:
btn.addEventListener('click', function (){
console.log('Click clone!')
})
得到的效果其實(shí)和上圖是一樣的,。也就是說,,克隆的時(shí)候,addEventListener() 綁定的事件並沒有被克隆,。
從上面的示例可以證明,,副本節(jié)點(diǎn)只能綁定使用內(nèi)聯(lián)方式綁定的事件處理函數(shù),。簡(jiǎn)單點(diǎn)說,,只有內(nèi)聯(lián)在HTML元素的事件,才會(huì)被cloneNode() 克隆,。
注意,,這個(gè)拷貝的節(jié)點(diǎn)並不在文檔中,需要自行添加到文檔中,。同時(shí)拷貝的節(jié)點(diǎn)有可能會(huì)導(dǎo)致節(jié)點(diǎn)的的 id 屬性重複,,最好修改新節(jié)點(diǎn)的 id ,而 name 屬性也可能重複,,自行決定是否需要修改,。
節(jié)點(diǎn)修改
DOM節(jié)點(diǎn)修改有關(guān)的API有:
node.appendChild() :插入一個(gè)新節(jié)點(diǎn)
node.insertBefore() :插入一個(gè)新節(jié)點(diǎn)
node.removeChild() :刪除一個(gè)節(jié)點(diǎn)
node.replaceChild() :替換一個(gè)節(jié)點(diǎn)
其中node.appendChild 和node.insertBefore 屬於DOM增中的新節(jié)點(diǎn)插入,而removeChild 屬於DOM中的刪,,replaceChild 屬於DOM中的改,。這一節(jié),咱們只先聊增這一部分,對(duì)於刪和改,,我們後面會(huì)單獨(dú)介紹,。
node.appendChild
parentNode.appendChild(child) 方法將一個(gè)節(jié)點(diǎn)child 添加到指定的父節(jié)點(diǎn)parentNode 的子節(jié)點(diǎn)列表的末尾。本方法返回值為要插入的這個(gè)節(jié)點(diǎn),。
let pEle = document.createElement('p')
pEle.textContent = '我是新添加的p元素'
document.body.appendChild(pEle)
上面的示例創(chuàng)建了一個(gè)新的段落元素pEle ,,然後使用appendChild() 將這個(gè)新創(chuàng)建的元素添加到body 的最末尾。
使用appendChild() 方法的時(shí)候有一點(diǎn)需要注意,。如果被插入的節(jié)點(diǎn)已經(jīng)存在文檔樹中,,則節(jié)點(diǎn)會(huì)被從原先的位置移除,並插入到新的位置,。當(dāng)然,,被移動(dòng)的元素被綁定的事件也會(huì)被同步過去,比如:
<div id="old">
<p id="move">我是一個(gè)段落元素</p>
</div>
<div id="new"></div>
<button id="btn">創(chuàng)建元素</button>
let pEle = document.getElementById('move')
let newEle = document.getElementById('new')
let btnEle = document.getElementById('btn')
pEle.addEventListener('click', function() {
console.log('click me!')
})
btnEle.addEventListener('click', function () {
pEle.textContent = '我是新添加的p元素'
newEle.appendChild(pEle)
})
如果要保留原來的這個(gè)子節(jié)點(diǎn)的位置,,則可以用 Node.cloneNode 方法複製出一個(gè)節(jié)點(diǎn)的副本,,然後再插入到新位置。這個(gè)方法只能將某個(gè)子節(jié)點(diǎn)插入到同一個(gè)文檔的其他位置,,如果你想跨文檔插入,,需要先調(diào)用document.importNode 方法。還有,,如果appendChild() 方法的參數(shù)是DocumentFragment 節(jié)點(diǎn),,那麼插入的是DocumentFragment 的所有子節(jié)點(diǎn),而不是DocumentFragment 節(jié)點(diǎn)本身,。此時(shí),,返回值是一個(gè)空的DocumentFragment 節(jié)點(diǎn)。
node.insertBefore
parentNode.insertBefore(child, referenceNode) 方法將一個(gè)節(jié)點(diǎn)child 插入作為父節(jié)點(diǎn)parentNode 的一個(gè)子節(jié)點(diǎn),,並且位置在參考節(jié)點(diǎn)referenceNode 之前,。
如果第二個(gè)參數(shù)referenceNode 為null ,則插入位置你父節(jié)點(diǎn)的末尾:
parentNode.insertBefore(node, null);
// 等價(jià)於
parentNode.appendChild(node);
注意,,第二個(gè)參數(shù)為null 時(shí)不能省略,,否則會(huì)報(bào)錯(cuò)。
來看一個(gè)小示例:
<div id="parent">
我是父節(jié)點(diǎn)
<p id="child">我是舊的子節(jié)點(diǎn)</p>
</div>
<button id="btn">插入節(jié)點(diǎn)</button>
let parentEle = document.getElementById('parent')
let childEle = document.getElementById('child')
let btnEle = document.getElementById('btn')
btnEle.addEventListener('click', function () {
let newEle = document.createElement('span')
newEle.textContent = '我是新添加節(jié)點(diǎn)的文本內(nèi)容'
parentEle.insertBefore(newEle, childEle)
})
使用這個(gè)方法可以模擬prependChild ,,產(chǎn)生類似於appendChild() ,,但是將節(jié)點(diǎn)插入作為指定父節(jié)點(diǎn)的第一個(gè)子節(jié)點(diǎn):
Node.prototype.prependChild = function (node) {
return this.insertBefore(node, this.firstChild)
}
let parentEle = document.getElementById('parent')
let btnEle = document.getElementById('btn')
btnEle.addEventListener('click', function () {
let newEle = document.createElement('p')
newEle.textContent = '我是新添加節(jié)點(diǎn)的文本內(nèi)容'
parentEle.prependChild(newEle)
})
其實(shí)這個(gè)效果和前面的效果是類似的。同樣的,,使用這個(gè)方法還可以模擬insertAfter ,,將節(jié)點(diǎn)要插在父節(jié)點(diǎn)的某個(gè)子節(jié)點(diǎn)後面:
Node.prototype.insertAfter = function(node, referenceNode) {
return this.insertBefore(node, referenceNode.nextSibling);
}
和 appendChild 類似,如果插入的節(jié)點(diǎn)是文檔中已經(jīng)存在的節(jié)點(diǎn),,則會(huì)移動(dòng)該節(jié)點(diǎn)到指定位置,,並且保留其綁定的事件,。
DOM的刪
DOM節(jié)點(diǎn)的刪除主要API是node.removeChild ??梢允褂?code>parentNode.removeChild(child)刪除指定父節(jié)點(diǎn)parentNode 的一個(gè)子節(jié)點(diǎn)child ,,並返回被刪除的節(jié)點(diǎn)。
這個(gè)方法是要在被刪除的節(jié)點(diǎn)的父節(jié)點(diǎn)上調(diào)用的,,而不是在被刪除節(jié)點(diǎn)上調(diào)用的,,如果參數(shù)節(jié)點(diǎn)不是當(dāng)前節(jié)點(diǎn)的子節(jié)點(diǎn),removeChild 方法將報(bào)錯(cuò):
// 通過 parentNode 屬性直接刪除自身
var node = document.getElementById('deleteDiv');
if (node.parentNode) {
node.parentNode.removeChild(node);
}
// 也可以封裝以下作為一個(gè)方法直接使用:
Node.prototype.remove = function(node) {
if (node.parentNode) {
return node.parentNode.removeChild(node);
}
throw new Error('Can not delete.');
}
node.remove();
使用這個(gè)方法也可以很簡(jiǎn)單的模擬 removeAllChild :
Node.prototype.removeAllChild = function() {
var deleteNode = []
while (this.firstChild) {
deleteNode.push(this.removeChild(this.firstChild));
}
return deleteNode;
}
被移除的這個(gè)子節(jié)點(diǎn)仍然存在於內(nèi)存中,,只是不在當(dāng)前文檔的 DOM 中,,仍然還可以被添加回文檔中。但是如果不使用一個(gè)變量保存這個(gè)節(jié)點(diǎn)的引用,,被刪除的節(jié)點(diǎn)將不可達(dá),,會(huì)在某次垃圾回收被清除。
DOM的改
parentNode.replaceChild(newChild, oldChild) 方法用指定的節(jié)點(diǎn)newChild 替換當(dāng)前節(jié)點(diǎn)parentNode 的一個(gè)子節(jié)點(diǎn)oldChild ,,並返回被替換的節(jié)點(diǎn)oldChild ,。
<div id="parent">
<p id="child">我是舊的第一個(gè)子節(jié)點(diǎn)</p>
</div>
<button id="btn">替換節(jié)點(diǎn)</button>
let parentEle = document.getElementById('parent')
let oldEle = document.getElementById('child')
let btnEle = document.getElementById('btn')
btnEle.addEventListener('click', function () {
let newEle = document.createElement('p')
newEle.setAttribute('id', 'newChild')
newEle.textContent = '我是新添加節(jié)點(diǎn)的文本內(nèi)容'
parentEle.replaceChild(newEle, oldEle)
})
簡(jiǎn)單的總結(jié)一下
DOM中的節(jié)點(diǎn)操作對(duì)應(yīng)的主要API有:
appendChild() :用於向childNodes 列表的末尾添加一個(gè)節(jié)點(diǎn)。返回新增的節(jié)點(diǎn),。
insertBefore() :接收兩個(gè)參數(shù):要插入的節(jié)點(diǎn)和作為參照的節(jié)點(diǎn),。插入節(jié)點(diǎn)後,被插入的節(jié)點(diǎn)會(huì)變成參照節(jié)點(diǎn)的前一個(gè)同胞節(jié)點(diǎn),。同時(shí)被方法返回,。
replaceChild() :接收兩個(gè)參數(shù):要插入的節(jié)點(diǎn)和要替換的節(jié)點(diǎn)。要替換的節(jié)點(diǎn)將由這個(gè)方法返回並從文檔樹中移除,。同時(shí)由要插入的節(jié)點(diǎn)佔(zhàn)據(jù)其位置,。
removeChild() :接收一個(gè)參數(shù),即要移除的節(jié)點(diǎn),。返回被移除的節(jié)點(diǎn),。
這四個(gè)方法都是操作的某個(gè)節(jié)點(diǎn)的子節(jié)點(diǎn),也就是說,,要使用這幾個(gè)方法必須先取得父節(jié)點(diǎn),。另外並不是所有節(jié)點(diǎn)都有子節(jié)點(diǎn),如果在不支持子節(jié)點(diǎn)的節(jié)點(diǎn)上,,調(diào)用了這些方法,將會(huì)導(dǎo)致錯(cuò)誤,。
DOM的查
DOM節(jié)點(diǎn)中的查主要包括:查找元素(類似於CSS中的選擇器)和節(jié)點(diǎn)查找,。
查找元素
先來看DOM中怎麼查找到元素,也就是說選擇到你想要的元素,。在DOM中查找元素(選擇到想要的元素)對(duì)應(yīng)的API主要有:
document.getElementById(id) :匹配特定id 的元素
document.getElementsByName(name) :根據(jù)給定的name 返回一個(gè)在 (X)HTML document 的NodeList 集合
document.getElementsByTagName(tagName) :返回一個(gè)包括所有給定標(biāo)籤名稱的元素的HTML集合HTMLCollection
document.getElementsByClassName(className) :返回包含了所有指定類名的子元素的類數(shù)組對(duì)象
document.querySelector(selector) :返回文檔中與指定選擇器或選擇器組匹配的第一個(gè)Element
document.querySelectorAll(selector) :返回與指定的選擇器組匹配的文檔中的元素列表,。返回的對(duì)象是NodeList
假設(shè)我們有一個(gè)簡(jiǎn)單的DOM文檔:
<div id="box">
<h3>Title</h3>
<ul class="list">
<li class="item">Item1</li>
<li class="item">Item2</li>
<li class="item">Item3</li>
<li class="item">Item4</li>
<li class="item">Item5</li>
</ul>
<p id="intro">Intro ...</p>
</div>
為了更好的說明前面的幾個(gè)API,,後續(xù)中的示例,都會(huì)採(cǎi)用這個(gè)DOM結(jié)構(gòu),。其對(duì)應(yīng)的DOM樹不再繪製了,。
document.getElementById(id)
document.getElementById(id) 返回的是一個(gè)Element 對(duì)象,用來匹配文檔中指定的id 元素,。如果沒有找到對(duì)應(yīng)的元素,,該方法會(huì)返回null 。另外,,document.getElementById() 方法不會(huì)搜索不在文檔中的元素,。當(dāng)創(chuàng)建一個(gè)元素,並且分配id 後,,必須要使用insertBefore 或其他類似的方法把元素插入到文檔之後才能使用document.getElementById() 獲取到,。
來看一個(gè)示例:
let idEle = document.getElementById('intro')
let btnEle = document.getElementById('btn')
btnEle.addEventListener('click', function () {
console.log(`能匹配到的: ${idEle}`)
console.log(idEle)
let newEle = document.createElement('section')
newEle.id = 'main'
newEle.textContent = '我是新添加的元素'
console.log(`未插入到DOM的新元素newEle: ${document.getElementById('main')}`)
console.log(document.getElementById('main'))
let box = document.getElementById('box')
box.insertBefore(newEle, idEle)
console.log(`插入到DOM的新元素newEle: ${document.getElementById('main')}`)
console.log(document.getElementById('main'))
})
來看輸出的結(jié)果:
比如上面示例,通過document.getElementById() 之後,,咱們獲取了DOM上的節(jié)點(diǎn),,這個(gè)時(shí)候可以對(duì)該節(jié)點(diǎn)做很多事情,比如查詢內(nèi)容和屬性,,或者其他任何操作,,甚至可以刪除它,克隆它,,或者將它移動(dòng)到DOM樹的其它節(jié)點(diǎn)上,。
注意,document.getElementById(id) 中的id 參數(shù)是有大小寫敏感的,,所以document.getElementById('Intro') 無(wú)法獲取到元素<p id="intro">Intro ...</p> ,。另外還有就是,如果文檔中有多個(gè)相同的id (這種情形一般不存在)時(shí),,只會(huì)返回第一個(gè),。
document.getElementsByName(name)
document.getElementsByName(name) 將根據(jù)給定的name 返回一個(gè)在document 的節(jié)點(diǎn)列表集合。name 屬性只有在HTML文檔中可用,。該方法返回的是一個(gè)NodeList 集合,,這個(gè)集合包含name 屬性為指定值的所有元素,比如<meta> ,、<object> ,,甚至那些不支持name 屬性但是添加了name 自定義屬性的元素也包含其中。
該方法常用於取得單選按鈕,。同樣也會(huì)返回HTMLCollection 對(duì)象,。HTMLCollection 對(duì)象可以通過length 屬性訪問元素長(zhǎng)度,通過[] 方括號(hào)語(yǔ)法訪問對(duì)象中的項(xiàng),。方括號(hào)中既可以是數(shù)字,,也可以是字符串索引值,。
document.getElementsByTagName(tagName)
document.getElementsByTagName(tagName) 將會(huì)返回一個(gè)包括所有給定標(biāo)籤名稱tagName 的元素的HTML集合HTMLCollection 。整個(gè)文件結(jié)構(gòu)都會(huì)被搜索,,包括根節(jié)點(diǎn),。返回的HTML集合是動(dòng)態(tài)的,意味著它可以自動(dòng)更新來保持和DOM樹同步,,而不用再次調(diào)用document.getElementsByTagName(tagName) ,。
let liEle = document.getElementsByTagName('li')
let btnEle = document.getElementById('btn')
btnEle.addEventListener('click', function () {
console.log(`能匹配到的: ${liEle}`)
console.log(liEle.length)
Object.keys(liEle).forEach(key => {
console.log(key, liEle[key])
})
})
比如上面的示例,通過getElementsByTagName('li') 獲取了文檔中所有的<li> 元素,。其開始於一個(gè)具體的父元素並且從它自上而下遞歸地在DOM樹中搜索符合標(biāo)籤名稱參數(shù)的子元素,。剛才也說了,其返回的是一個(gè)動(dòng)態(tài)的HTMLCollection 對(duì)象,。獲得這個(gè)對(duì)象之後,,可以對(duì)其做一些遍歷操作。比如上面使用Object.keys() 遍歷出li :
有關(guān)於JavaScript中對(duì)象遍歷相關(guān)的操作可以閱讀《如何遍歷JavaScript中對(duì)象屬性》和《對(duì)象屬性的枚舉》,。
有一點(diǎn)需要注意,,調(diào)用 getElementsByTagName() 的不是那個(gè)文件節(jié)點(diǎn) document ,事實(shí)上是使用這個(gè)方法 element.getElementsByTagName() ,。
document.getElementsByClassName(className)
document.getElementsByClassName(className) 返回一個(gè)包含了所有指定類名的子元素的類數(shù)組對(duì)象,。當(dāng)在document 對(duì)象上調(diào)用時(shí),會(huì)搜索整個(gè)DOM文檔,,包含根節(jié)點(diǎn),。你也可以在任意元素上調(diào)用getElementsByClassName() 方法,它將返回的是以當(dāng)前元素為根節(jié)點(diǎn),,所有指定類名的子元素,。
比如,獲取所有class 為item 的元素:
document.getElementsByClassName('item')
如果你想獲取多個(gè)class 的元素時(shí),,可以用空格來隔開,,比如說,同時(shí)獲取所有class 同時(shí)包括btn 和btn-lg 的元素:
document.getElementsByClassName('btn btn-lg')
如果你想獲取某個(gè)元素的子節(jié)點(diǎn)中對(duì)應(yīng)class 的元素時(shí),,你也可以像下面這樣操作:
document.getElementById('box').getElementsByClassName('item')
document.querySelector(selector)
document.querySelector(selector) 方法可以幫助你選擇一個(gè)HTML元素,。如果選擇了多個(gè)HTML元素,其總是返回第一個(gè)元素,。它看起來像這樣:
document.querySelector('li')
使用這個(gè)方法可以通過id ,、class 以及標(biāo)籤元素,甚至是元素的一些屬性可以選擇一個(gè)元素,。
- 使用一個(gè)
id 選擇元素,,需要在id 前使用#
- 使用一個(gè)
class 選擇元素,需要在class 前使用.
- 使用一個(gè)標(biāo)籤選擇元素,,可以直接把元素標(biāo)籤當(dāng)作選擇器
甚至為了更好的理解或者記憶,,只要滿足CSS的選擇器,那麼都可以被運(yùn)用於document.querySelector(selector) 中的selector 選擇器,。
document.querySelectorAll(selector)
document.querySelectorAll(selector) 可以幫助你選擇多個(gè)元素,。這個(gè)方法中的selector 和document.querySelector() 具有相同的語(yǔ)法。唯一不同的是,,你可以通過用逗號(hào), 分隔來選擇多個(gè)元素,。
比如:
var matches = document.querySelectorAll("div.note, div.alert");
節(jié)點(diǎn)查找
DOM中節(jié)點(diǎn)共有12種類型,每種類型分別表示文檔中不同的信息標(biāo)記,。每個(gè)節(jié)點(diǎn)都擁有各自的特點(diǎn),、數(shù)據(jù)和方法,也與其他節(jié)點(diǎn)存在某種關(guān)係,。節(jié)點(diǎn)之間的關(guān)係構(gòu)成了層次,,而所有頁(yè)面標(biāo)記則表現(xiàn)為一個(gè)以特定節(jié)點(diǎn)為根節(jié)點(diǎn)的樹形結(jié)構(gòu)。用張圖來描述:
所有的節(jié)點(diǎn)都有這些屬性,,都是可以用於訪問相關(guān)的node 節(jié)點(diǎn):
Node.childNodes : 訪問一個(gè)單元素下所有的直接子節(jié)點(diǎn)元素,,可以是一個(gè)可循環(huán)的類數(shù)組對(duì)象。該節(jié)點(diǎn)集合可以保護(hù)不同的類型的子節(jié)點(diǎn)(比如text節(jié)點(diǎn)或其他元素節(jié)點(diǎn)),。
Node.firstChild : 與childNodes 數(shù)組的第一個(gè)項(xiàng)(Element.childNodes[0] )是同樣的效果,,僅僅是快捷方式。
Node.lastChild : 與childNodes 數(shù)組的最後一個(gè)項(xiàng)(Element.childNodes[Element.childNodes.length-1] )是同樣的效果,,僅僅是快捷方式,。
Node.parentNode : 訪問當(dāng)前節(jié)點(diǎn)的父節(jié)點(diǎn),父節(jié)點(diǎn)只能有一個(gè),,祖節(jié)點(diǎn)可以用Node.parentNode.parentNode 的形式來訪問,。
Node.nextSibling : 訪問DOM樹上與當(dāng)前節(jié)點(diǎn)同級(jí)別的下一個(gè)節(jié)點(diǎn)。
Node.previousSibling : 訪問DOM樹上與當(dāng)前節(jié)點(diǎn)同級(jí)別的上一個(gè)節(jié)點(diǎn),。
用張圖來闡述,,會(huì)更清晰:
通過這張圖,理解起來就簡(jiǎn)單多了,,但有個(gè)非常重要的知識(shí)點(diǎn):那就是元素之間不能有空格,,如果ul 和li 之間有空格的話,就會(huì)被認(rèn)為是內(nèi)容為空的text node 節(jié)點(diǎn),,這樣ul.childNodes[0] 就不是第一個(gè)li 元素了,。相應(yīng)地,<p> 的下一個(gè)節(jié)點(diǎn)也不是<ul> ,,因?yàn)?code><p>和<ul> 之間有一個(gè)空行的節(jié)點(diǎn),,一般遇到這種情況需要遍歷所有的子節(jié)點(diǎn)然後判斷nodeType 類型。
根據(jù)上面的描述,我們可以把DOM中的節(jié)點(diǎn)相互之間存在著的各種關(guān)係分為:父子關(guān)係,,兄弟關(guān)係等:
父關(guān)係相關(guān)的API
parentNode :每個(gè)節(jié)點(diǎn)都有一個(gè)parentNode 屬性,,它表示元素的父節(jié)點(diǎn)。Element 的父節(jié)點(diǎn)可能是Element ,,Document 或DocumentFragment ,;如果不存在,則返回null
parentElement :返回元素的父元素節(jié)點(diǎn),,與parentNode 的區(qū)別在於,,其父節(jié)點(diǎn)必須是一個(gè)Element 元素,如果不是,,則返回null ,;
子關(guān)係API
children :返回一個(gè)實(shí)時(shí)的HTMLCollection ,子節(jié)點(diǎn)都是Element ,;保存的是該節(jié)點(diǎn)的第一層元素子節(jié)點(diǎn)
childNodes :返回一個(gè)實(shí)時(shí)的NodeList ,,表示元素的子節(jié)點(diǎn)列表,注意子節(jié)點(diǎn)可能包含文本節(jié)點(diǎn),、註釋節(jié)點(diǎn)等,;
firstChild :返回第一個(gè)子節(jié)點(diǎn),不存在返回null ,,與之相對(duì)應(yīng)的還有一個(gè)firstElementChild ,;
lastChild :返回最後一個(gè)子節(jié)點(diǎn),不存在返回null ,,與之相對(duì)應(yīng)的還有一個(gè)lastElementChild ,;
兄弟關(guān)係型API
previousSibling :節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn),如果不存在則返回null ,。注意有可能拿到的節(jié)點(diǎn)是文本節(jié)點(diǎn)或註釋節(jié)點(diǎn),,與預(yù)期的不符,要進(jìn)行處理一下,。
nextSibling :節(jié)點(diǎn)的後一個(gè)節(jié)點(diǎn),,如果不存在則返回null 。注意有可能拿到的節(jié)點(diǎn)是文本節(jié)點(diǎn),,與預(yù)期的不符,,要進(jìn)行處理一下。
previousElementSibling :返回前一個(gè)元素節(jié)點(diǎn),,前一個(gè)節(jié)點(diǎn)必須是Element ,。
nextElementSibling :返回後一個(gè)元素節(jié)點(diǎn),後一個(gè)節(jié)點(diǎn)必須是Element ,。
總結(jié)
DOM操作在JavaScript還是很重要的,,簡(jiǎn)單點(diǎn)說,所有的交互操作都是基於DOM來操作的。而DOM中的操作,,最為熟悉的就是對(duì)DOM的增,、刪、改,、查,。今天的內(nèi)容也就圍繞著這幾個(gè)方面展開,。因?yàn)樯婕暗降膬?nèi)容較多,,可能會(huì)有遺漏或者說零亂。如果你覺得上面不對(duì)之處,,還請(qǐng)路過大神指正,。
|