跳至主要内容

getElementsByTagName() 手寫實作

getElementsByTagName() 是一個 DOM API,可以用來取得指定標籤名稱的 DOM 元素,並且會回傳一個 HTMLCollection 物件。

這裡會在不使用 document.querySelectorAll()的情況下 手寫 Element.getElementsByTagName() 方法

輸入參數

  • 一個 element 作為起始搜尋的根節點。
  • 一個 tag 字串。

功能需求

  • 搜尋 element 的所有後代(不包含自身)是否包含指定的 tag 名稱。
  • 如果節點的 tag 包含了所有指定的 tag 名稱,就將其加入結果陣列中。
  • 最終返回一個陣列(Array<Element>),而非類陣列(HTMLCollection)。

範例

const doc = new DOMParser().parseFromString(
`<div id="foo">
<span>Span</span>
<p>Paragraph</p>
<div id="bar">Div</div>
</div>`,
"text/html",
);

getElementsByTagName(doc.body, "div");
// [div#foo, div#bar]

實作思路:

由於 DOM 是樹狀結構,透過 遞迴 進行可以方便地遍歷所有的子節點及其後代,並將符合條件的節點加入陣列中。

export default function getElementsByTagName(
element: Element,
tagNameParam: string,
): Array<Element> {
//用於存放符合條件的後代元素
const elements: Array<Element> = [];

//將輸入的 tagNameParam 轉為大寫,因為 Element.tagName 返回的值是大寫(如 DIV)。
const tagName = tagNameParam.toUpperCase();

//使用遞迴的方式,逐一檢查 element 的所有後代節點。
function traverse(el: Element) {
if (el == null) {
return;
}
//如果目前的節點標籤名稱符合目標,加入陣列
if (el.tagName === tagName) {
elements.push(el);
}

// 遍歷目前節點的所有子節點
for (const child of el.children) {
traverse(child);
}
}

//搜尋後代節點(不包括起始節點),必須從 element.children 開始遍歷。
//Element.children 會回傳即時子元素的 HTMLCollection
for (const child of element.children) {
traverse(child);
}

return elements;
}

HTMLCollection

HTMLCollection 是一種 動態更新 的類陣列,當 DOM 結構發生變化時,HTMLCollection 的內容會即時反映這些變化。

為什麼不能用.forEach()遍歷? 因為 HTMLCollection 是一個類陣列,不是真正的陣列,因此不繼承 Array.prototype 上的方法(例如 .forEach, .map, .filter 等)。