LESSON-16 · 零基礎學生教材
JavaScript 基礎:DOM 與動態渲染
完成依資料數量產生服務清單的 renderServices 函式,理解 DOM 節點建立與安全文字寫入,不把資料當成 HTML 執行。
先理解:這堂課解決什麼問題?
服務資料已經整理好,現在要讓頁面依資料產生卡片,新增資料時不用手動複製 HTML。
本堂要完成:完成依資料數量產生服務清單的 renderServices 函式,理解 DOM 節點建立與安全文字寫入,不把資料當成 HTML 執行。
學習邊界:先完成「用 DOM API 將服務資料安全地渲染到頁面」,不提前引入尚未教過的框架或建置工具。
第一次上課環境:先建立四個觀察位置
- 編輯器:只修改目前階段
files/index.html、files/style.css與files/main.js;不要猜測不存在的services.html或js/main.js。 - 瀏覽器:先開啟
examples/minimal.html,再依序開啟 Starter、Checkpoint、Solution 的files/index.html;每次儲存後重新整理。 - DevTools:Elements 看 DOM 節點與文字,Console 看 ReferenceError/TypeError 與實際筆數;Network 只用來確認
main.js已載入,不把載入成功當成渲染成功。 - 操作習慣:每次只做一個資料→DOM 修改,先預測 li 數量與文字,再儲存、重新整理,最後記下畫面、Elements 與 Console 證據。
先做最小實驗:先看見結果
這不是期末網站,而是一個可以直接看到本堂核心結果的最小實驗。先完成一次操作,再回到檔案和名詞卡對照。
- 入口:開啟
examples/minimal.html。 - 操作:開啟
examples/minimal.html,先觀察三筆服務,再按「顯示空清單」與「恢復服務」,最後用 Elements 查看 li 與文字節點。 - 預期:三筆資料會產生三個 li;空陣列會顯示空狀態;恢復後仍只有三筆,特殊 <strong> 標記只會顯示成文字。
- 重設:重新整理
examples/minimal.html,回到三筆 services 的初始資料。
學生練習流程
照這六步走;每一步先完成指定操作,再用「完成判斷」確認結果,最後才進入下一步。
-
1
先看見結果
觀察三筆服務、status、特殊 <strong> 字串與空清單狀態,先知道完成後要看見什麼。
完成判斷:Elements 有三個 li,空陣列有狀態,特殊標記仍是純文字。
開啟最小實驗 -
2
補齊必要名詞
回到名詞卡,理解 DOM、selector、
querySelector、createElement、textContent與 replaceChildren 的責任。完成判斷:能說出資料來源、DOM 目標與畫面結果各自負責什麼。
查看名詞卡 -
3
完成 Starter
依序建立
#service-list/#status、services 與 selector,再完成 renderServices 並呼叫它。完成判斷:每完成一個微步驟,就重新整理並留下畫面或 Console 證據。
進入 Starter -
4
修正 Checkpoint
先觀察 innerHTML 讓特殊字串變成真正 DOM 子元素,再只把指定寫入方式改成
textContent。完成判斷:第一筆 <strong> 標記以文字顯示,li 不再多出 strong 子元素。
進入 Checkpoint -
5
對照 Solution
逐檔比較
index.html、style.css與main.js,確認資料、DOM、status 與 Console 的完整路徑。完成判斷:三個 li、status 文字、Elements 節點與 Console 無錯誤互相一致。
進入 Solution -
6
完成 FAQ 變體
把 services 的資料→DOM 方法移植到 FAQ,再測試空陣列與 testimonials 挑戰。
完成判斷:FAQ 兩筆資料形成 li/h2/p,資料內容沒有被當成 HTML 解析。
開啟 FAQ 變體
檔案地圖:每個位置負責什麼?
先不要急著背檔名;請把檔案位置和責任連起來。你在階段頁會看到同一份檔案的可執行版本。
examples/minimal.html只讀最小實驗:先觀察完整的資料→DOM 渲染、空清單與重複渲染。examples/faq-variant.html情境變體:把 services 流程改成 FAQ,確認方法可移植。starter/files/index.htmlStarter HTML:依序建立#service-list與 #status。starter/files/main.jsStarter JavaScript:依序準備資料、渲染函式與呼叫流程。checkpoint/files/main.jsCheckpoint JavaScript:故意用 innerHTML,供學生重現安全性缺口。solution/files/main.jsSolution JavaScript:以textContent完成安全渲染與可觀察 status。
先學會這些詞
先從名詞、英文與白話意思開始;每次只展開一張名詞卡,再對照正式定義、範例與驗收證據。專業術語、語法、檔案路徑會使用不同樣式。第一次遇到術語時,先回到這裡,不要靠猜。
DOM 與選取
5 個詞DOMDocument Object Model 白話意思:瀏覽器把 HTML 解析成 JavaScript 可以找到與修改的文件樹。 展開查看正式定義、範例與驗收證據
- 正式定義
- 代表目前文件節點、階層與內容狀態的程式介面。
- 什麼時候用
- 要讓 JavaScript 找到清單、按鈕或狀態文字時。
- 最小範例
- document.querySelector("#service-list") 找到頁面上的清單元素。
- 可觀察證據
- Elements 看到的節點階層、節點數量與畫面內容會隨程式更新。
- 容易混淆
- 和原始 HTML 文字混淆;DOM 是瀏覽器解析後目前正在使用的文件狀態。
元素element 白話意思:頁面中的一個完整內容單位,例如 h1、ul 或 li。 展開查看正式定義、範例與驗收證據
- 正式定義
- 瀏覽器建立的文件節點,通常由 HTML 標籤與其內容形成。
- 什麼時候用
- 要說明 JavaScript 找到或建立的是哪一個頁面單位時。
- 最小範例
- document.createElement("li") 建立一個新的 li 元素。
- 可觀察證據
- Elements 可以看到元素的標籤、屬性、子節點與文字。
- 容易混淆
- 和標籤混淆;標籤是 HTML 的寫法,元素是瀏覽器建立的節點單位。
selectorselector 白話意思:告訴 CSS 或 JavaScript 要找哪一個元素的寫法。 展開查看正式定義、範例與驗收證據
- 正式定義
- 用來匹配文件元素的模式;本堂先使用 id selector,例如
#service-list。 - 什麼時候用
- 要讓
querySelector精準找到 HTML 目標時。 - 最小範例
- #service-list 會尋找 id 為 service-list 的元素。
- 可觀察證據
- selector 和 HTML 的 id 完全一致時,
querySelector不會得到 null。 - 容易混淆
- 和 property 混淆;selector 決定找誰,property 是 CSS 要改變的設定名稱。
querySelectorquerySelector 白話意思:依照一個 selector 找到頁面中的第一個元素。 展開查看正式定義、範例與驗收證據
- 正式定義
- Document 與 Element 提供、以 CSS selector 字串查找第一個符合節點的方法。
- 什麼時候用
- 要把 JavaScript 連到
#service-list或#status時。 - 最小範例
- const list = document.querySelector("#service-list");。
- 可觀察證據
- Console 沒有因 null 而報錯,且 list 的文字或子節點會真的變化。
- 容易混淆
- 和 querySelectorAll 混淆;本堂一次只找一個目標,後續再學多個結果。
nullnull 白話意思:代表「目前沒有找到物件」的特殊值。 展開查看正式定義、範例與驗收證據
- 正式定義
- JavaScript 表示刻意沒有物件值的原始值;
querySelector找不到目標時會回傳 null。 - 什麼時候用
- 畫面沒有更新,或要判斷 selector 是否找到元素時。
- 最小範例
- const list = document.querySelector("#missing"); 的結果是 null。
- 可觀察證據
- Console 顯示 null,接著若直接呼叫
list.append()會指出哪一行發生錯誤。 - 容易混淆
- 和 undefined 混淆;null 常表示明確的空物件,undefined 常表示尚未提供值。
建立與寫入
5 個詞createElementDocument.createElement() 白話意思:在 JavaScript 裡建立一個還沒放進頁面的新元素。 展開查看正式定義、範例與驗收證據
- 正式定義
- Document 介面用來建立指定 HTML 標籤元素節點的方法。
- 什麼時候用
- 資料有幾筆不固定,不能手動複製固定數量 HTML 時。
- 最小範例
- const card = document.createElement("li");。
- 可觀察證據
- 在 Elements 看到新的 li 節點,而且節點數量和資料筆數一致。
- 容易混淆
- 和 append 混淆;
createElement建立節點,append 才把節點接到文件裡。
textContentNode.textContent 白話意思:讀取或替換元素裡的純文字。 展開查看正式定義、範例與驗收證據
- 正式定義
- Node 提供、代表節點文字內容並可更新文字的屬性;它不會把字串解析成 HTML。
- 什麼時候用
- 資料來自使用者或外部來源,只需要顯示文字而不需要插入標籤時。
- 最小範例
- card.textContent = item.name + "|" + item.price + " 元";。
- 可觀察證據
<strong>這類字串會原樣出現在畫面文字中,Elements 不會多出 strong 子元素。- 容易混淆
- 和 innerHTML 混淆;
textContent寫文字,innerHTML 會解析 HTML 標記。
innerHTMLElement.innerHTML 白話意思:把字串當成 HTML 讀取或寫入元素內容。 展開查看正式定義、範例與驗收證據
- 正式定義
- Element 提供、以 HTML markup 序列化或解析子樹內容的屬性。
- 什麼時候用
- 只有在內容來源和 markup 都是可信且確實需要 HTML 結構時,才由熟悉者使用。
- 最小範例
- card.innerHTML = item.name; 會把名稱中的 <strong> 當成標籤解析。
- 可觀察證據
- 比較 Elements 的子元素與
card.textContent;特殊字串若變成 strong 節點就表示被解析了。 - 容易混淆
- 和
textContent混淆;初學者渲染資料時優先使用textContent。
appendElement.append() 白話意思:把一個或多個節點或文字接到元素的最後面。 展開查看正式定義、範例與驗收證據
- 正式定義
- Element 介面用來將 Node 或字串附加為子內容的方法。
- 什麼時候用
- 已經建立 li 後,要把它放進
#service-list時。 - 最小範例
- list.append(card); 把 card 放到清單最後。
- 可觀察證據
- Elements 的子節點順序和資料陣列順序一致。
- 容易混淆
- 和
createElement混淆;append 不負責建立節點,只負責接上去。
replaceChildrenElement.replaceChildren() 白話意思:清空元素目前的子內容,準備重新放入一批結果。 展開查看正式定義、範例與驗收證據
- 正式定義
- Element 介面以指定節點取代所有現有子節點的方法;沒有參數時會清空。
- 什麼時候用
- 同一個渲染函式會被重複呼叫,且不希望舊清單和新清單重複時。
- 最小範例
- list.replaceChildren(); 先清空,再用 forEach 逐筆 append。
- 可觀察證據
- 重新渲染後 li 數量仍等於資料筆數,不會每按一次就多一批重複內容。
- 容易混淆
- 和 removeChild 混淆;本堂一次清理整個容器,不逐筆追蹤舊節點。
資料與載入
4 個詞forEachArray.prototype.forEach() 白話意思:依序對陣列中的每一筆資料執行一次函式。 展開查看正式定義、範例與驗收證據
- 正式定義
- Array 的迭代方法,依陣列順序對每個元素呼叫 callback,不建立新的陣列結果。
- 什麼時候用
- 要把 services 的每一筆資料轉成一個 li 節點時。
- 最小範例
- services.forEach((item) => { /* 建立一個 li */ });。
- 可觀察證據
- 資料有三筆就執行三次,Elements 會有三個 li,且順序相同。
- 容易混淆
- 和 map 混淆;forEach 重點是逐筆執行副作用,map 重點是產生新陣列。
deferscript defer 白話意思:讓外部 JavaScript 延後到 HTML 解析完成後執行。 展開查看正式定義、範例與驗收證據
- 正式定義
- script 元素的布林屬性;外部腳本可在背景下載,並於文件解析完成後依序執行。
- 什麼時候用
- 腳本需要找到 body 裡的元素,又希望保留外部
main.js檔案時。 - 最小範例
- <script src="main.js" defer></script>。
- 可觀察證據
- 重新整理時不會因 HTML 尚未建立就找不到目標;Network 能看到
main.js載入,Console 沒有時序錯誤。 - 容易混淆
- 和 async 混淆;本堂先用 defer 保持文件順序與可預測的執行時機。
資料陣列/物件array and object 白話意思:陣列保存多筆資料,物件用欄位描述其中一筆資料。 展開查看正式定義、範例與驗收證據
- 正式定義
- array 是有順序的集合;object 是以 property
key/value描述一個資料單位。 - 什麼時候用
- 畫面數量會隨資料增減,不想為每一筆手寫 HTML 時。
- 最小範例
- const services = [{ name: "到府整理", price: 1200 }];。
- 可觀察證據
- Console 可讀到 services.length、item.name 與 item.price,畫面項目數與陣列長度一致。
- 容易混淆
- 和畫面上的 li 混淆;資料是來源,DOM 元素是渲染後的結果。
role=statusARIA status role 白話意思:告訴輔助技術這段文字是會更新的狀態訊息。 展開查看正式定義、範例與驗收證據
- 正式定義
- ARIA role,用來標示具有即時性但通常不需打斷使用者的狀態區域。
- 什麼時候用
- 渲染完成、空清單或操作結果需要被讀到時。
- 最小範例
- <p id="status" role="status">尚未渲染</p>。
- 可觀察證據
- Elements 可看到 role,#status 文字會隨渲染結果更新。
- 容易混淆
- 和單純的 class 混淆;class 主要是樣式或定位,role 是語意與輔助技術資訊。
最小例子:先看懂,再複製
這段只保留本堂第一個微成果。先預測結果,再逐行讀;完整檔案請開啟下方最小實驗。
<ul id="service-list" aria-label="服務方案清單"></ul>
<p id="status" role="status" aria-live="polite">尚未渲染</p>Live Lab:逐段讀碼與驗證
每一步只回答五件事:為什麼做、改哪裡、會看到什麼、用什麼證據確認、卡住先查什麼。
-
Step 1
先建立 DOM 的目標位置
- 目的
- 先讓 HTML 提供可被程式找到的 ul 與 status,避免把資料和畫面位置混在一起。
- 修改位置
starter/files/index.html的#service-list與 #status
加入 ul#service-list、role=status 與 aria-live=polite,並讓 id 和後續 selector 完全一致。- 預期結果
- Elements 看到一個空的 ul 與一個 status 段落;Console 尚未有渲染錯誤。
- 驗收證據
- Elements 的 DOM 階層、id 唯一性與 role=status 屬性。
- 卡住先查
- 只搜尋
index.html的 service-list、status 是否各出現一次。
-
Step 2
先把資料和 DOM 查找接起來
- 目的
- 理解 services 是資料來源,
list/status是畫面出口,三者是不同責任。 - 修改位置
starter/files/main.js的檔案開頭
宣告三筆 services,並用querySelector找到#service-list與 #status。- 預期結果
- 程式有資料來源與兩個 DOM 參考;尚未呼叫渲染時,畫面仍是空清單。
- 驗收證據
- 檔案中的變數、Console 的 services.length,以及 Elements 的目標節點。
- 卡住先查
- 只看三個
const的名稱和querySelectorselector 是否逐字對得上 HTML。
-
Step 3
把一筆資料轉成一個 li
- 目的
- 用
createElement、textContent、append 和 forEach 形成最小渲染循環。 - 修改位置
starter/files/main.js的 renderServices
先清空 list,再逐筆建立 li,以textContent寫入內容,最後 append 到清單。- 預期結果
- 函式定義完成;呼叫後三筆資料會變成三個 li,且特殊字串仍是純文字。
- 驗收證據
- Elements 的 li 數量、文字節點、順序與函式內四個 DOM API 的責任。
- 卡住先查
- 只檢查 list.replaceChildren、
createElement、textContent、append 是否在同一個 forEach 流程中。
-
Step 4
把函式接到實際頁面結果
- 目的
- 區分「宣告函式」和「呼叫函式」,並讓 status 與 Console 提供驗收出口。
- 修改位置
starter/files/main.js的 renderServices(services)
在函式宣告後呼叫 renderServices(services),更新 status,並印出實際筆數。- 預期結果
- 重新整理後看到三個 li,#status 顯示已渲染 3 筆,Console 沒有 ReferenceError 或 TypeError。
- 驗收證據
- 畫面 li 數量、#status 文字、Console 訊息與無紅色錯誤。
- 卡住先查
- 搜尋 renderServices(services) 是否出現在函式宣告之後,且 services 已經宣告。
-
Step 5
重現 innerHTML 和
textContent的差異- 目的
- 用一個可觀察的特殊字串理解安全文字寫入,不把「看起來正常」當成安全證據。
- 修改位置
checkpoint/files/main.js的 card 寫入行
先用 Elements 比較 childElementCount,再把 card.innerHTML 改成 card.textContent。- 預期結果
- 第一筆的 <strong> 標記會以文字顯示,li 不會多出 strong 子元素。
- 驗收證據
- Elements 的 childElementCount、innerHTML 與
textContent前後比較。 - 卡住先查
- 只看 renderServices 內 card 的那一行,不要先修改資料或 HTML。
-
Step 6
把同一套流程移植到 FAQ
- 目的
- 確認學生學到的是資料→DOM 的方法,而不是只會複製 services 這個名稱。
- 修改位置
examples/faq-variant.html的 faq 陣列與 #faq-list
用createElement建立 li、h2、p,分別用textContent放入 question 與 answer,再 append 到 #faq-list。- 預期結果
- FAQ 兩筆資料變成兩個 li,每個 li 有 h2 與 p,資料內容沒有被當 HTML 解析。
- 驗收證據
- FAQ 入口畫面、Elements 的
li/h2/p階層、status 文字與 Console 無錯誤。 - 卡住先查
- 先確認目前 URL 是
examples/faq-variant.html,再對照 faq-list 是否和querySelector相同。
讀碼順序:先找入口,再用畫面、Elements、Console 與檔案行號驗證;本堂重點是資料如何變成 DOM,不用硬編碼通過訊息取代實際結果。
互動問答
先回答目前這一題,再展開解析;完成後按下一題,讓每一次回答都回到檔案或瀏覽器完成驗證。
Q1概念理解 「DOM(Document Object Model)」在本堂要解決什麼問題? 本題線索:DOM;回到名詞卡與 Elements 看到的節點階層、節點數量與畫面內容會隨程式更新。 展開解析收合解析
白話解析:
瀏覽器把 HTML 解析成 JavaScript 可以找到與修改的文件樹。 正式來說,代表目前文件節點、階層與內容狀態的程式介面。
對照位置:
回到名詞卡「DOM」,再看 Elements 看到的節點階層、節點數量與畫面內容會隨程式更新。
預期觀察:
你應該能用自己的話說出:要讓 JavaScript 找到清單、按鈕或狀態文字時。
常見錯誤:
不要只回答「它是DOM」;那是重複名詞,不是說明責任。
立即驗證:
開啟 examples/minimal.html,依序做「開啟 examples/minimal.html,先觀察三筆服務,再按「顯示空清單」與「恢復服務」,最後用 Elements 查看 li 與文字節點。」並記下畫面或工具證據。
Q2程式碼閱讀
修改指定規則後,你預測畫面會看到什麼?
本題線索:starter/files/index.html 的 #service-list 與 #status;要修改:加入 ul#service-list、role=status 與 aria-live=polite,並讓 id 和後續 selector 完全一致。;觀察:Elements 的 DOM 階層、id 唯一性與 role=status 屬性。
展開解析收合解析
白話解析:
先看檔案責任,再預測結果。這一步的目的:先讓 HTML 提供可被程式找到的 ul 與 status,避免把資料和畫面位置混在一起。
對照位置:
對照 Live Lab Step 1,位置是 starter/files/index.html 的 #service-list 與 #status;範例內容:<ul id="service-list" aria-label="服務方案清單"></ul>
<p id="status" role="status" aria-live="polite">尚未渲染</p>
預期觀察:
Elements 看到一個空的 ul 與一個 status 段落;Console 尚未有渲染錯誤。
常見錯誤:
如果只改了檔案但沒有結果,先不要重寫全部;只搜尋 index.html 的 service-list、status 是否各出現一次。
立即驗證:
實際操作後檢查 Elements 的 DOM 階層、id 唯一性與 role=status 屬性。。
Q3概念比較
「DOM」和「元素」在本堂的責任有什麼不同?
本題線索:比較 DOM 與 元素;操作位置:starter/files/main.js 的檔案開頭
展開解析收合解析
白話解析:
DOM:瀏覽器把 HTML 解析成 JavaScript 可以找到與修改的文件樹。;元素:頁面中的一個完整內容單位,例如 h1、ul 或 li。
對照位置:
對照兩張名詞卡的正式定義:代表目前文件節點、階層與內容狀態的程式介面。/瀏覽器建立的文件節點,通常由 HTML 標籤與其內容形成。
預期觀察:
你應該能指出兩者分別出現在哪個檔案或工具,以及哪一個結果會改變。
常見錯誤:
不要把「改變畫面」當成所有技術的責任;先說清楚誰負責結構、呈現、行為或驗收。
立即驗證:
在 starter/files/main.js 的檔案開頭 做「宣告三筆 services,並用 querySelector 找到 #service-list 與 #status。」,再用 檔案中的變數、Console 的 services.length,以及 Elements 的目標節點。 比較前後差異。
Q4除錯驗收
如果完成操作後結果不對,你會先從哪一個證據開始查?
本題線索:examples/faq-variant.html 的 faq 陣列與 #faq-list;第一個檢查位置:FAQ 入口畫面、Elements 的 li/h2/p 階層、status 文字與 Console 無錯誤。
展開解析收合解析
白話解析:
先描述症狀,再提出一個最小假設。本堂常見錯誤是:直接複製兩份 HTML,或把整份 FAQ 陣列直接指定給 innerHTML。
對照位置:
對照最後一段 Live Lab:examples/faq-variant.html 的 faq 陣列與 #faq-list;檢查方式:先確認目前 URL 是 examples/faq-variant.html,再對照 faq-list 是否和 querySelector 相同。
預期觀察:
你要能指出一個具體位置,而不是一次修改很多檔案。
常見錯誤:
不要先清快取、重裝工具或複製 Solution;先查看第一個可觀察錯誤。
立即驗證:
重新操作並留下「症狀、證據、假設、單一修正、結果」五項紀錄。
除錯對照表:先看哪個證據?
| 看到的症狀 | 第一個證據 | 先檢查什麼 |
|---|---|---|
| 畫面完全沒有清單 | Console 的第一個錯誤 | 先看是否有 services is not defined、list is null 或函式沒有被呼叫。 |
| li 數量一直增加 | Elements 的子節點數 | 確認 renderServices 開頭是否先呼叫 replaceChildren()。 |
| <strong> 變成粗體標籤 | Elements 的 DOM 子節點 | 比較 innerHTML 與 textContent,確認資料寫入方法。 |
| Console 顯示 null | HTML 的 id 與 querySelector | 逐字比對 #service-list、#status,再確認 script 使用 defer。 |
常見誤解
- 看到畫面沒有變化,不代表程式沒執行;先確認檔案、路徑、元素與狀態證據。
- 能複製 Solution 不代表理解;請先說出修改哪個檔案、預期哪個結果、用什麼證據確認。
- 本堂只使用原生 HTML、CSS、JavaScript 與瀏覽器 DevTools;不加入 Vue、fetch、API、非同步請求或建置工具,先把資料→DOM 的單一路徑做清楚。
本堂任務與驗收
完成依資料數量產生服務清單的 renderServices 函式,理解 DOM 節點建立與安全文字寫入,不把資料當成 HTML 執行。
- 能用 Starter 留下可重現證據。
- 能用 Checkpoint 留下可重現證據。
- 能用 Solution 入口可開啟 留下可重現證據。
- 能用 Elements 清單筆數與資料一致 留下可重現證據。
- 能用 特殊字串以文字顯示 留下可重現證據。
- 能用 空陣列有狀態 留下可重現證據。
- 能用 Console 無錯誤 留下可重現證據。
- Starter、Checkpoint、Solution 的入口都能開啟並知道三者差異。
- 遇到問題時能先寫出症狀,再檢查一個最小假設。
官方延伸閱讀
下載講師版 Markdown:需要授課或離線查閱時使用,內容包含完整教學節奏。