Minghsin University of Science and Technology
Uedu Main Site
Explore Uedu
Student Console
Register as Member/Login
Research Informed Consent Center
Survey Center
Educational Big Data Challenge
Claim or create a course
Try a Scenario Task
Support & Messages
Uptime Data

UeduGPTs

--

Jupyters

1

Local AI

--

Uedu Code

--

明新科大 AQI 70 24°C PM2.5 19
AI Reply Desktop Notifications

Show a desktop notification when the AI TA finishes replying

Chat Message Notifications

Notify me when classmates post messages in the forum

Sound notification

Play an alert sound whenever there is a new notification

Unit 17

DOM 元素集合

NodeList、HTMLCollection 與轉成陣列
Interaction實作 15 minute(s)

Learning Objectives

  • 用 querySelectorAll 取得 NodeList,並使用 length、forEach、item、entries 等屬性與方法
  • 說明 getElementsByTagName、getElementsByClassName、children 回傳的 HTMLCollection,以及 children 與 childNodes 的差別
  • 分辨動態集合(live)與靜態集合(static):DOM 改變之後,哪一種集合會跟著更新
  • 說明 NodeList 與 HTMLCollection 是類陣列,並用 Array.from、展開運算子轉成陣列後使用 map、filter

一次取得一群元素

網頁上常常有一整群相同的元素:清單的每一項、表格的每一列、一排商品卡片。 querySelectorAll、getElementsByTagName 這類方法一次取得全部,回傳的是一個集合。

集合看起來很像陣列:有 length,可以用 items[0] 取得第一個。但它不是陣列, 這一課要弄清楚三件事:

  1. 集合有兩種:NodeList 與 HTMLCollection,各有不同的方法
  2. 有些集合會隨著 DOM 改變自動更新(動態),有些不會(靜態)
  3. 想用陣列的 map、filter,要先把集合轉成真正的陣列

querySelectorAll 與 NodeList

document.querySelectorAll(選擇器) 回傳所有符合 CSS 選擇器的元素,結果是 NodeList。 選擇器可以用逗號分隔好幾個,例如 querySelectorAll('div.note, div.alert')。找不到時回傳長度 0 的 NodeList(不是 null)。

NodeList 的屬性與方法 作用
length 節點的數量
item(i) 第 i 個節點,等同 list[i]
forEach(fn) 逐一處理每個節點,用法與陣列的 forEach 相同
entries() 逐一取得 [索引, 節點]
keys()/values() 逐一取得索引/節點

NodeList 也可以直接放進 for...of 迴圈:for (const li of items) { … }。

互動練習:操作 NodeList

範例用 querySelectorAll 取得三個 <li>,主控台會顯示 NodeList(3) […]。依任務在 JavaScript 分頁最後面寫程式,按「執行」。

HTMLCollection 與 children

getElementsByTagName、getElementsByClassName,以及元素的 children 屬性,回傳的是 HTMLCollection (投影片把 getElementsByName 也列在這裡;依現行規格,它回傳的是會自動更新的 NodeList):

HTMLCollection 的屬性與方法 作用
length 元素的數量
item(i) 第 i 個元素
namedItem('email') id 或 name 是 email 的元素

children 與 childNodes:兩者都是「子節點」,差別在包含什麼:

屬性 回傳 包含
el.children HTMLCollection 只有元素節點
el.childNodes NodeList 所有節點,包含文字節點(標籤之間的換行與空白也算)、註解節點

兩種集合的比較:

HTMLCollection NodeList
怎麼取得 getElementsByTagName、getElementsByClassName、children querySelectorAll、childNodes、getElementsByName
內容 只有元素 可能有各種節點
方法 item、namedItem item、forEach、entries、keys、values
DOM 改變之後 動態:自動更新 querySelectorAll 的結果是靜態,不會更新;childNodes 是動態

動態集合(live collection)像一扇窗,隨時反映 DOM 現在的樣子;靜態集合(static collection)像一張照片, 停在取得的那一刻。querySelectorAll 屬於 W3C 的 Selectors API 規範,回傳的就是靜態 NodeList。

互動練習:動態集合與靜態集合

範例用兩種方法取得同一個清單的 <li>:live 是 HTMLCollection,fixed 是 querySelectorAll 的 NodeList,現在兩者都是 2。新增一個 <li> 之後再印一次,看看誰會變。

想一想:NodeList 與 HTMLCollection

1.

ul 原本有 2 個 <li>。執行下面的程式後,a.length 與 b.length 分別是多少?

const a = ul.children;
const b = ul.querySelectorAll('li');
ul.append(document.createElement('li'));
2.

HTML 是下面這樣,box.children.length 與 box.childNodes.length 分別是多少?

<div id="box">
  <p>一</p>
</div>
3.

下列哪一個方法回傳的是 HTMLCollection?

第 1 次作答

NodeList 不是陣列

NodeList 與 HTMLCollection 都是類陣列(array-like):有編號的項目與 length,但不是真正的陣列。 Array.isArray(items) 是 false,陣列的 push、pop、map、filter、reduce 都不能用 (NodeList 只多了 forEach;HTMLCollection 連 forEach 都沒有)。

想用陣列的方法,先把集合轉成陣列:

const items = document.querySelectorAll('li');

const a = Array.from(items);                       // 最常用
const b = [...items];                               // 展開運算子(集合必須可迭代)
const c = Array.prototype.slice.call(items);        // 舊程式碼常見的寫法
const d = [];
for (const item of items) { d.push(item); }         // 用 for...of 一個一個放進陣列

Array.from 還可以傳第二個參數,在轉換時順便處理每一項:Array.from(items, li => li.textContent)。

下面的範例用一個自己寫的類陣列物件示範(執行動畫在背景執行緒裡跑,沒有網頁可以取元素)。 NodeList 和它一樣有索引與 length;不同的是 NodeList 可以迭代,所以也能用展開運算子。

類陣列轉成陣列

fake 有 0、1、2 與 length,看起來像陣列,但 fake.map 是 undefined。注意第 7 行之後右邊多出的 arr1:它才是真正的陣列,可以用 map。最後的展開運算子會出錯,被 catch 接住。

互動練習:轉成陣列再計算

四個商品的價格寫在 data-price。先體驗直接對 NodeList 呼叫 map 會怎樣,再轉成陣列完成計算。

想一想:類陣列

1.

下面的程式會印出什麼?

const fake = { 0: 'a', 1: 'b', length: 2 };
console.log(Array.from(fake).length, typeof fake.forEach);
2.

const items = document.querySelectorAll('li'),下列哪一個寫法會出現 TypeError?

3.

document.getElementsByTagName('p').forEach(p => p.remove()) 會怎樣?

第 1 次作答

實作:轉成陣列再篩選

scores 是一個類陣列物件,就像從網頁取得的集合一樣不能直接用 filter。請把它轉成陣列,篩出 60 分以上的分數,依序印出及格的分數陣列與及格人數。

你的輸出
預期輸出

重點精華回顧

  • querySelectorAll 回傳 NodeList,有 length、item、forEach、entries、keys、values,也可以用 for...of。
  • getElementsByTagName、getElementsByClassName、children 回傳 HTMLCollection,只有元素節點,方法只有 item 與 namedItem。
  • children 只包含元素;childNodes 包含所有節點,標籤之間的換行與空白也是文字節點。
  • HTMLCollection 與 childNodes 是動態集合,DOM 改變會自動反映;querySelectorAll 的 NodeList 是靜態集合,停在取得的那一刻。
  • NodeList 與 HTMLCollection 是類陣列,不是陣列:沒有 map、filter、push。
  • 用 Array.from(集合)、[...集合]、Array.prototype.slice.call(集合) 或 for...of 轉成陣列後,就能使用陣列的所有方法。
這一課完成了!
Next lesson:window 與 document
你目前沒有登入,學習進度只存在這個瀏覽器。 Login 後,進度會跟著帳號,換電腦也看得到。