JavaScript DOM 查找元素 (DOM Traversing)

DOM document Object

document 物件是 DOM tree 的根節點,表示整份 HTML 文件,通常你要存取 HTML 都是從 document 物件開始。

DOM Model

從上圖可以發現,DOM 中的節點類型除了 HTML 元素節點 (element nodes) 外,還有文字節點 (text nodes),另外還有註解節點 (comment nodes) 和空白節點 (whitespace nodes,也是一種 text node)。

document.getElementById(id)

document.getElementById 用來根據 id 取得一個 HTML 元素。

例如這一個 HTML 頁面:

<html>
<head>
  <title>getElementById example</title>
</head>
<body>
  <p id="para">Some text here</p>
</body>
</html>

我們可以這樣取得其中 id 為 para 的 p 元素:

var elem = document.getElementById('para');

document.getElementsByTagName(name)

document.getElementsByTagName 用來根據 HTML 標籤 (tag) 名稱取得所有這個標籤的元素集合 (HTMLCollection),返回的結果是一個像陣列 (array) 的物件。

每個 HTML DOM 元素也有 getElementsByTagName 方法 - node.getElementsByTagName(name),用來找該元素下面的子元素。

例如這一個 HTML 頁面:

<html>
<head>
  <title>getElementsByTagName example</title>
</head> 
<body>
  <p>Some outer text</p>
  <p>Some outer text</p>      

  <div id="div1">
    <p>Some div1 text</p>
    <p>Some div1 text</p>
    <p>Some div1 text</p>     

    <div id="div2">
      <p>Some div2 text</p>
      <p>Some div2 text</p>
    </div>
  </div>

  <p>Some outer text</p>
  <p>Some outer text</p>
</body>
</html>

我們可以用 JavaScript 這樣存取 HTML 元素:

function getAllParaElems() {
    // 取得所有的 <p> 元素
    var allParas = document.getElementsByTagName('p');

    // 取得總共有幾個 <p> 元素
    // num = 9
    var num = allParas.length;

    // .....
}

function div1ParaElems() {
    // 取得 id 為 div1 的 HTML 元素
    var div1 = document.getElementById('div1');
    
    // 取得 div1 下面所有的 <p> 元素
    var div1Paras = div1.getElementsByTagName('p');
    
    // 取得 div1 下面總共有幾個 <p> 元素
    // num = 5
    var num = div1Paras.length;
    
    // .....
}

function div2ParaElems() {
    // 取得 id 為 div1 的 HTML 元素
    var div2 = document.getElementById('div2');

    // 取得 div2 下面所有的 <p> 元素
    var div2Paras = div2.getElementsByTagName('p');
    
    // 取得 div2 下面總共有幾個 <p> 元素
    // num = 2
    var num = div2Paras.length;

    // .....
}

document.getElementsByName(name)

document.getElementsByName 用來取得特定名稱 (name) 的 HTML 元素集合 (NodeList Collection),返回的結果是一個像陣列 (array) 的物件。

例如這一個 HTML 頁面:

<html>
<head>
  <title>getElementsByName example</title>
</head> 
<body>
  <form name="up"><input type="text"></form>
  <div name="down"><input type="text"></div>
</body>
</html>

我們可以用 JavaScript 這樣存取 HTML 元素:

// 取得 name 是 up 的元素
var upForms = document.getElementsByName('up');

// 輸出 "FORM"
console.log(upForms[0].tagName);

document.getElementsByClassName(names)

document.getElementsByClassName 用來取得特定類別名稱 (class name) 的 HTML 元素集合 (HTMLCollection),返回的結果是一個像陣列 (array) 的物件。

每個 HTML DOM 元素也有 getElementsByClassName 方法 - node.getElementsByClassName(names),用來找該元素下面的子元素。

例如這一個 HTML 頁面:

<html>
<head>
  <title>getElementsByClassName example</title>
</head> 
<body>
  <div id="parent-id">
    <p>hello word1</p>
    <p class="test">hello word2</p>
    <p>hello word3</p>
    <p>hello word4</p>
  </div>
</body>
</html>

我們可以用 JavaScript 這樣存取 HTML 元素:

// 取得 id 為 parent-id 的 HTML 元素
var parentDOM = document.getElementById('parent-id');

// 取得 parentDOM 下面所有 class 是 test 的 HTML 元素
var test = parentDOM.getElementsByClassName('test');

// 輸出 1
console.log(test.length)

// 輸出 <p class="test">hello word2</p>
console.log(parentDOM.getElementsByClassName('test')[0]);

可以用空白隔開多個 class name,元素必須有所有指定的 class name 才符合。例如:

// 取得同時有 red 和 test 兩個 class name 的所有元素
document.getElementsByClassName('red test');

IE 在 IE9 開始才有支援 getElementsByClassName 方法。

document.querySelector(selectors)

document.querySelector 讓你可以用 CSS 選擇器 (CSS selectors) 來尋找符合條件且第一個找到的 HTML 元素。

每個 HTML DOM 元素也有 querySelector 方法 - node.querySelector(selectors),用來找該元素下面的子元素。

例如:

// 找出 class name 是 foo 的 HTML 元素
var el1 = document.querySelector('.foo');

// 找出 class name 有 user-panel 和 main 的 <div> 元素下面,name 屬性是 login 的 <input> 子元素 
var el2 = document.querySelector('div.user-panel.main input[name=login]');

IE 在 IE8 開始才有支援 querySelector 方法。

document.querySelectorAll(selectors)

document.querySelectorAll 讓你可以用 CSS 選擇器 (CSS selectors) 來尋找所有符合條件的 HTML 元素集合 (NodeList)。

每個 HTML DOM 元素也有 querySelectorAll 方法 - node.querySelectorAll(selectors),用來找該元素下面的子元素。

例如:

// 找出所有 class name 是 foo 或 bar 的 <div> 元素
var elems1 = document.querySelectorAll('div.foo, div.bar');

// 找出所有的 <p> 元素
var elems2 = document.querySelectorAll('p');

IE 在 IE8 開始才有支援 querySelectorAll 方法,且在 IE8 只支援 CSS2 selectors。

DOM tree 節點間位置的相互關係

我們先用一張圖來說明 DOM 元素節點之間的相互關係 - 什麼稱作父節點 (parent)、子節點 (child) 和兄弟節點 (sibling):

Node.childNodes

所有的 DOM 節點物件都有 childNodes 屬性 (read-only property),可以用來取得該元素下的所有子元素集合 (NodeList)。

使用範例:

// 取得 id 為 foo 的元素
var foo = document.getElementById('foo');

// 元素的 hasChildNodes() 方法可以用來判斷一個元素下有沒子元素
if (foo.hasChildNodes()) {

    // 取得 foo 元素的所有子元素集合
    var children = foo.childNodes;

    // 可以用 for 迴圈來遍歷每一個子元素
    for (var i=0; i<children.length; ++i) {
        // 用 children[i] 來取得遍歷到子元素
    }
}

childNodes 返回的子元素除了 HTML 元素節點外,還有文字節點 (text nodes)、註解節點 (comment nodes) 和空白節點 (whitespace nodes,除了 IE<9)。

Node.children

DOM 節點物件的 children 屬性和 childNodes 屬性類似,差異在於 childNodes 返回的子元素會包含文字節點 (text nodes) 和註解節點 (comment nodes),children 屬性則只會返回 HTML 元素節點 (HTMLCollection)。

要特別留意的是,IE<9 會返回 comment nodes。

Node.firstChild

DOM 節點物件的 firstChild 屬性可以用來取得其下的第一個子節點或返回 null 表示沒有任何子節點。

用法:

<p id="foo">
  <span>First span</span>
</p>

<script>
    var p = document.getElementById('foo');
    // 會顯示 "#text",因為第一個子元素是空白的文字節點
    alert(p.firstChild.nodeName);
</script>

另外一個例子:

<p id="foo"><span>First span</span></p>

<script>
    var p = document.getElementById('foo');
    // 會顯示 "SPAN"
    alert(p.firstChild.nodeName);
</script>

Node.lastChild

DOM 節點物件的 lastChild 屬性可以用來取得其下的最後一個子節點或返回 null 表示沒有任何子節點。

範例:

<p id="foo"><span>First span</span><span>Second span</span><span>Last span</span></p>

<script>
    var p = document.getElementById('foo');
    // 會顯示 "Last span"
    alert(p.lastChild.innerHTML);
</script>

Node.parentNode

DOM 節點物件的 parentNode 屬性可以用來取得其父元素,得到值可能會是一個元素節點 (Element node)、根節點 (Document node) 或 DocumentFragment 節點。

例如:

<p><span id="foo">my span</span></p>

<script>
    var ele = document.getElementById('foo');
    // 會顯示 "P"
    alert(ele.parentNode.nodeName);
</script>

Node.previousSibling

DOM 節點物件的 previousSibling 屬性用來取得在目前節點前面的兄弟節點 (sibling),返回 null 表示該節點已經是第一個子節點。

用法:

<div><span id="s1">s1</span><span id="s2">s2</span></div>

<script>
    // 會顯示 null
    alert(document.getElementById('s1').previousSibling);

    // 會顯示 "s1"
    alert(document.getElementById('s2').previousSibling.id);
</script>

Node.nextSibling

DOM 節點物件的 nextSibling 屬性用來取得在目前節點後面的兄弟節點 (sibling),返回 null 表示該節點已經是最後一個子節點。

範例:

<div><span id="s1">s1</span><span id="s2">s2</span></div>

<script>
    // 會顯示 "s2"
    alert(document.getElementById('s1').nextSibling.id);

    // 會顯示 null
    alert(document.getElementById('s2').nextSibling);
</script>

元素集合的查找結果是動態 (live) 的

上面介紹的很多 DOM 查找方式會返回一個元素集合,是一個像陣列的物件 - 有 length 屬性、可以用 for 迴圈遍歷結果、可以用索引 (index) 取得特定元素。

但只有都有 length 和 index 這部分和陣列相似而已,其實返回的資料型態是 NodeList 或 HTMLCollection 物件。

而 NodeList 和 HTMLCollection 的差別在於,NodeList 包含任何的節點類型,HTMLCollection 則只包含 HTML 元素節點 (Element nodes)。

另外一個特點在於所有 getElementsBy* 方法返回的元素集合的結果是動態的 (live)!什麼意思?就是隨後在 DOM 上面新增、修改、刪除節點的操作,都會直接反應在先前得到的元素集合結果中。

舉個例子:

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

<script>
    var outerDiv = document.getElementById('outer');
    var divs = document.getElementsByTagName('div')

    // 顯示 2
    alert(divs.length);

    // 清空 outer 下的節點
    outerDiv.innerHTML = '';

    // 顯示 1
    alert(divs.length);
</script>