經過DOM API 查找節點

在上一篇的分享當中,咱們簡單介紹了BOM 與DOM,也瞭解到JavaScript 是怎麼經過它們提供的方法來與瀏覽器進行溝通。javascript

當一個網頁被載入到瀏覽器時,瀏覽器會首先分析這個HTML 文檔,而後會依照這份HTML 的內容解析成DOM (Document Object Model,即文件對象模型)。html

而DOM 是W3C 制定的一個規範,它是獨立於平臺與語言的標準。換言之,只要遵照這樣的規範,不論是什麼平臺或者是什麼語言開發,均可以經過DOM 提供的API 來操做DOM 的內容、結構與樣式。java

因此說,DOM 是網頁的根本,懂得控制DOM就能夠控制整個網頁,作出良好的互動體驗node

那麼在今天的分享中,咱們就繼續來介紹DOM API 查找節點的方法吧。git

前言:script標籤放哪裏有區別嗎?

針對<script>標籤放哪裏,這個題目其實沒有標準答案,通常你會看到有兩種版本:github

  • 放在<head> ... </head>之間
  • 放在</body>以前

還有人會說爲何放在<head> ... </head>裏面的JavaScript沒有做用?我以爲這說的有出入,這裏咱們簡單來說一下問題所在。數組

那麼咱們來試試上一篇介紹過的,先以document.querySelector取得id="hello"的節點,而後經過textContent來修改內容。瀏覽器

先來試試把<script>標籤放在</body>以前。在jsbin裏面立刻執行看看,看起來彷佛很ok呢!網站

接着,咱們試着把<script>標籤移到<head> ... </head>之間:ui

咦?怎麼什麼都沒有呢?並且也沒有錯誤信息,JavaScript真的如你們說的同樣,很垃圾嗎?

冷靜一下,容我解釋一下。

前面說過,當一個網頁被載入到瀏覽器時,瀏覽器會先分析這個HTML 文檔,由上而下依序來讀取解析:

因此上面jsbin例子中,當瀏覽器在<head> ... </head>之間遇到<script>標籤時,就會暫停解析網頁,而且當即執行<script>裏的內容,直到script執行完畢後再繼續解析網頁。

<head> ... </head>裏的<script>想要嘗試去尋找<div id="hello">這個標籤,但由於還沒解析到網頁本體,因此也無從取得。

不是瀏覽器壞掉,也不是JavaScript太渣,而是由於咱們不理解瀏覽器執行的原理所形成的誤會

這裏是瀏覽器加載一個有 <script> 標籤的網站所發生的事情:

  1. 拉取 HTML 頁面
  2. 開始解析 HTML
  3. 解析到 <script> 標籤以後準備獲取 script 文件.
  4. 瀏覽器獲取script文件。同時,html 解析中斷而且阻斷頁面上其餘html的解析。
  5. 一段時間後,script下載完成而且執行
  6. 繼續解析HTML文檔的其餘部分(解析script以後的html代碼)

第4步致使了很很差的用戶體驗,直到script文件所有下載完成以前HTML都不能獲得解析。

那麼,當咱們把<script>標籤放在</body>結束以前,因爲DOM已經解析完成,因此document.querySelector就能夠順利取得id="hello"的節點,而且把'HELLO'的字串放在網頁裏啦!

這樣提及來,<script>標籤是否是就不適合放在<head> ... </head>之間呢?

也不能這麼說,這點認真要講的話以後或許能夠用一整篇來講明這個。

DOM 節點的選取

上一篇文章說過,document對象是DOM tree的根節點,因此當咱們要存取HTML時,都從document對象開始。而DOM的節點類型除了HTML元素節點(element nodes)外,還有文字節點(text nodes)、註釋節點(comment nodes)等。

而常見的DOM 選取方法有下列這些:

// 根據傳入的值,找到 DOM 中 id 爲 'xxx' 的元素。
document.getElementById('xxx');

// 針對給定的 tag 名稱,返回全部符合條件的 NodeList 對象(節點的集合)
document.getElementsByTagName('xxx');

// 針對給定的 class 名稱,返回全部符合條件的節點集合
document.getElementsByClassName('xxx');

// 針對給定的 Selector 條件,返回第一個 或 全部符合條件的節點集合
document.querySelector('xxx'); 
document.querySelectorAll('xxx');
複製代碼

DOM 節點的類型

DOM 經常使用的節點類型有下面幾種:

能夠經過節點類型常數或是對應數值來判斷:

document.nodeType === Node.DOCUMENT_NODE;   //true
document.nodeType === 9;   //true
複製代碼

其餘不經常使用或是已經廢棄的部分能夠參考:MDN Node.nodeType一節。

DOM 節點間的查找遍歷(Traversing)

因爲DOM 節點有分層的概念,因而節點與節點之間的關係,咱們大體上能夠分紅如下兩種:

  • 父子關係:除了document以外,每個節點都會有個上層的節點,咱們一般稱之爲「父節點」 (Parent node),而相對地,從屬於本身下層的節點,就會稱爲「子節點」 (Child node)。
  • 兄弟關係:有同一個「父節點」的節點,那麼他們彼此之間就是「兄弟節點」(Siblings node)。

而隔層的節點基本上沒有直接關係。

上圖中水平方向的鄰層節點爲父子關係,垂直方向的同層節點爲兄弟關係。

Node.childNodes

全部的DOM節點對象都有childNodes屬性,且此種屬性沒法修改。

咱們能夠經過Node.hasChildNodes()來檢查某個DOM節點是否有子節點。

var node = document.querySelector('#hello');

// 若是 node 內有子元素
if( node.hasChildNodes() ) {
    
    // 能夠經過 node.childNodes[n] (n 爲數字索引) 取得對應的節點
    // 注意,NodeList 對象內容爲即時更新的集合
    for (var i = 0; i < node.childNodes[i].length; i++) {
       // ... 
    }; 
}
複製代碼

Node.childNodes返回的可能會有這幾種:

  • HTML 元素節點(element nodes)
  • 文字節點(text nodes),包含空格
  • 註釋節點(comment nodes)

Node.firstChild

Node.firstChild能夠取得Node節點的第一個子節點,若是沒有子節點則返回null

要注意的是,子節點包括空白節點,以下面例子:

<p>
  <span>span 1</span>
  <span>span 2</span>
  <span>span 3</span>
</p>

<script> var p = document.querySelector('p'); // tagName 屬性能夠取得 node 的標籤名稱 console.log(p.firstChild.tagName); // undefined </script>
複製代碼

由於取得的是<p>與第一個<span>中間的換行字元,因此p.firstChild.tagName會獲得undefined。因此改爲這樣:

<p><span>span 1</span><span>span 2</span><span>span 3</span></p>

<script> var p = document.querySelector('p'); // tagName 屬性能夠取得 node 的標籤名稱 console.log(p.firstChild.tagName); // "SPAN" </script>
複製代碼

把中間的換行與空白移除,就會獲得預期中的"SPAN"了。

Node.lastChild

Node.lastChild能夠取得Node節點的最後一個子節點,若是沒有子節點則返回null

Node.firstChild同樣的是,子節點也包括空白節點,因此像這樣:

<p>
  <span>span 1</span>
  <span>span 2</span>
  <span>span 3</span>
</p>

<script> var p = document.querySelector('p'); // textContent 屬性能夠取得節點內的文字內容 console.log(p.lastChild.textContent); // "" (換行字元) </script>
複製代碼

獲得的會是一個換行字元的空字符串。

移除節點之間多餘的空白後:

<p><span>span 1</span><span>span 2</span><span>span 3</span></p>

<script> var p = document.querySelector('p'); // textContent 屬性能夠取得節點內的文字內容 console.log(p.lastChild.textContent); // "span 3" </script>
複製代碼

輸出的就會是正確的"span 3" 啦。

Node.parentNode

那麼相較於Child系列,parentNode就單純一些。

經過Node.parentNode能夠用來取得父元素,返回值可能會是一個元素節點(Element node)、根節點(Document node)或DocumentFragment節點。

<p><span>span 1</span><span>span 2</span><span>span 3</span></p>

<script> var el = document.querySelector('span'); console.log( el.parentNode.nodeName ); // "P" </script>
複製代碼

Node.previousSibling

看完了DOM父與子以後,接着來看看兄弟節點。

經過Node.previousSibling能夠取得同層之間的前一個節點,若是node已是第一個節點且前面無節點,則返回null

<p><span>span 1</span><span>span 2</span><span>span 3</span></p>

<script> var el = document.querySelector('span'); console.log( el.previousSibling ); // null // document.querySelectorAll 會取得全部符合條件的集合, // 而 document.querySelectorAll('span')[2] 指的是「第三個」符合條件的元素。 var el2 = document.querySelectorAll('span')[2]; console.log( el2.previousSibling.textContent ); // "span 2" </script>
複製代碼

Node.nextSibling

Node.previousSibling相似,經過Node.nextSibling能夠取得同層之間的下一個節點,若是node已是最後一個節點,則返回null

<p><span>span 1</span><span>span 2</span><span>span 3</span></p>

<script> // document.querySelector 會取得第一個符合條件的元素 var el = document.querySelector('span'); console.log( el.nextSibling.textContent ); // "span 2" </script>
複製代碼

document.getElementsBy*與document.querySelector/ document.querySelectorAll的差別

今天分享了不少關於DOM的選取以及查找遍歷的方式,其中,像是document.getElementById以及document.querySelector由於取得的必定只會有一個元素/節點,因此不會有indexlength屬性。

document.getElementsBy**(注意,這裏有個s)以及document.querySelectorAll則分別返回HTMLCollection與NodeList。

這二者實際上是有點差異的,HTMLCollection只收集HTML element 節點,而NodeList除了HTML element 節點,也包含文字節點、屬性節點等。固然二者也有相似的地方,雖然不能使用數組的method,但這兩種均可以用數組索引的方式來存取內容,也就是僞數組。

另外一個須要注意的地方是,HTMLCollection/NodeList在大部分狀況下是即時更新的,但經過document.querySelector/document.querySelectorAll取得的NodeList是靜態的。

啥意思呢?舉個例子:

<div id="outer">
  <div id="inner">inner</div>
</div>

<script> // <div id="outer"> var outerDiv = document.getElementById('outer'); // 全部的 <div> 標籤 var allDivs = document.getElementsByTagName('div'); console.log(allDivs.length); // 2 // 清空 <div id="outer"> 下的節點  outerDiv.innerHTML = ''; // 由於清空了<div id="outer"> 下的節點,因此只剩下 outer console.log(allDivs.length); // 1 </script>
複製代碼

若是改爲document.querySelector的寫法:

<div id="outer">
  <div id="inner">inner</div>
</div>

<script> // <div id="outer"> var outerDiv = document.getElementById('outer'); // 全部的 <div> 標籤 var allDivs = document.querySelectorAll('div'); console.log(allDivs.length); // 2 // 清空 <div id="outer"> 下的節點  outerDiv.innerHTML = ''; // document.querySelector 返回的是靜態的 NodeList,不受 outerDiv 更新影響 console.log(allDivs.length); // 2 </script>
複製代碼

那麼以上就是今天所要介紹的內容啦。

在後續的文章會再繼續說明DOM API新增/刪除/修改節點的部分,歡迎持續關注。

若是以爲文章對你有些許幫助,歡迎在個人GitHub博客點贊和關注,感激涕零!

相關文章
相關標籤/搜索