商用網站架設實務

LESSON-16 · 零基礎學生教材

JavaScript 基礎:DOM 與動態渲染

完成依資料數量產生服務清單的 renderServices 函式,理解 DOM 節點建立與安全文字寫入,不把資料當成 HTML 執行。

先理解:這堂課解決什麼問題?

服務資料已經整理好,現在要讓頁面依資料產生卡片,新增資料時不用手動複製 HTML

本堂要完成:完成依資料數量產生服務清單的 renderServices 函式,理解 DOM 節點建立與安全文字寫入,不把資料當成 HTML 執行。

學習邊界:先完成「用 DOM API 將服務資料安全地渲染到頁面」,不提前引入尚未教過的框架或建置工具。

第一次上課環境:先建立四個觀察位置

  1. 編輯器:只修改目前階段 files/index.htmlfiles/style.cssfiles/main.js;不要猜測不存在的 services.htmljs/main.js
  2. 瀏覽器:先開啟 examples/minimal.html,再依序開啟 Starter、Checkpoint、Solution 的 files/index.html;每次儲存後重新整理。
  3. DevTools:Elements 看 DOM 節點與文字,Console 看 ReferenceError/TypeError 與實際筆數;Network 只用來確認 main.js 已載入,不把載入成功當成渲染成功。
  4. 操作習慣:每次只做一個資料→DOM 修改,先預測 li 數量與文字,再儲存、重新整理,最後記下畫面、Elements 與 Console 證據。

先做最小實驗:先看見結果

這不是期末網站,而是一個可以直接看到本堂核心結果的最小實驗。先完成一次操作,再回到檔案和名詞卡對照。

開啟本堂最小實驗

  1. 入口:開啟 examples/minimal.html
  2. 操作:開啟 examples/minimal.html,先觀察三筆服務,再按「顯示空清單」與「恢復服務」,最後用 Elements 查看 li 與文字節點。
  3. 預期:三筆資料會產生三個 li;空陣列會顯示空狀態;恢復後仍只有三筆,特殊 <strong> 標記只會顯示成文字。
  4. 重設:重新整理 examples/minimal.html,回到三筆 services 的初始資料。

學生練習流程

照這六步走;每一步先完成指定操作,再用「完成判斷」確認結果,最後才進入下一步。

  1. 1

    先看見結果

    觀察三筆服務、status、特殊 <strong> 字串與空清單狀態,先知道完成後要看見什麼。

    完成判斷:Elements 有三個 li,空陣列有狀態,特殊標記仍是純文字。

    開啟最小實驗
  2. 2

    補齊必要名詞

    回到名詞卡,理解 DOMselectorquerySelectorcreateElementtextContentreplaceChildren 的責任。

    完成判斷:能說出資料來源、DOM 目標與畫面結果各自負責什麼。

    查看名詞卡
  3. 3

    完成 Starter

    依序建立 #service-list/#status、services 與 selector,再完成 renderServices 並呼叫它。

    完成判斷:每完成一個微步驟,就重新整理並留下畫面或 Console 證據。

    進入 Starter
  4. 4

    修正 Checkpoint

    先觀察 innerHTML 讓特殊字串變成真正 DOM元素,再只把指定寫入方式改成 textContent

    完成判斷:第一筆 <strong> 標記以文字顯示,li 不再多出 strong 子元素

    進入 Checkpoint
  5. 5

    對照 Solution

    逐檔比較 index.htmlstyle.cssmain.js,確認資料、DOM、status 與 Console 的完整路徑。

    完成判斷:三個 li、status 文字、Elements 節點與 Console 無錯誤互相一致。

    進入 Solution
  6. 6

    完成 FAQ 變體

    把 services 的資料→DOM 方法移植到 FAQ,再測試空陣列與 testimonials 挑戰。

    完成判斷:FAQ 兩筆資料形成 li/h2/p,資料內容沒有被當成 HTML 解析。

    開啟 FAQ 變體

檔案地圖:每個位置負責什麼?

先不要急著背檔名;請把檔案位置和責任連起來。你在階段頁會看到同一份檔案的可執行版本。

先學會這些詞

先從名詞、英文與白話意思開始;每次只展開一張名詞卡,再對照正式定義、範例與驗收證據。專業術語語法檔案路徑會使用不同樣式。第一次遇到術語時,先回到這裡,不要靠猜。

本堂 14 個詞先選一組,再展開需要的名詞卡;每次只保留目前的學習焦點。

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 白話意思:告訴 CSSJavaScript 要找哪一個元素的寫法。 展開查看正式定義、範例與驗收證據
正式定義
用來匹配文件元素的模式;本堂先使用 id selector,例如 #service-list
什麼時候用
要讓 querySelector 精準找到 HTML 目標時。
最小範例
#service-list 會尋找 id 為 service-list 的元素。
可觀察證據
selectorHTML 的 id 完全一致時,querySelector 不會得到 null
容易混淆
property 混淆;selector 決定找誰,propertyCSS 要改變的設定名稱。
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:逐段讀碼與驗證

每一步只回答五件事:為什麼做、改哪裡、會看到什麼、用什麼證據確認、卡住先查什麼。

  1. Step 1

    先建立 DOM 的目標位置

    目的
    先讓 HTML 提供可被程式找到的 ul 與 status,避免把資料和畫面位置混在一起。
    修改位置
    starter/files/index.html#service-list 與 #status
    加入 ul#service-listrole=status 與 aria-live=polite,並讓 id 和後續 selector 完全一致。
    預期結果
    Elements 看到一個空的 ul 與一個 status 段落;Console 尚未有渲染錯誤。
    驗收證據
    ElementsDOM 階層、id 唯一性與 role=status 屬性。
    卡住先查
    只搜尋 index.html 的 service-list、status 是否各出現一次。
  2. Step 2

    先把資料和 DOM 查找接起來

    目的
    理解 services 是資料來源,list/status 是畫面出口,三者是不同責任。
    修改位置
    starter/files/main.js 的檔案開頭
    宣告三筆 services,並用 querySelector 找到 #service-list 與 #status。
    預期結果
    程式有資料來源與兩個 DOM 參考;尚未呼叫渲染時,畫面仍是空清單。
    驗收證據
    檔案中的變數、Console 的 services.length,以及 Elements 的目標節點。
    卡住先查
    只看三個 const 的名稱和 querySelector selector 是否逐字對得上 HTML
  3. Step 3

    把一筆資料轉成一個 li

    目的
    createElementtextContentappendforEach 形成最小渲染循環。
    修改位置
    starter/files/main.js 的 renderServices
    先清空 list,再逐筆建立 li,以 textContent 寫入內容,最後 append 到清單。
    預期結果
    函式定義完成;呼叫後三筆資料會變成三個 li,且特殊字串仍是純文字。
    驗收證據
    Elements 的 li 數量、文字節點、順序與函式內四個 DOM API 的責任。
    卡住先查
    只檢查 list.replaceChildrencreateElementtextContentappend 是否在同一個 forEach 流程中。
  4. 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 已經宣告。
  5. Step 5

    重現 innerHTMLtextContent 的差異

    目的
    用一個可觀察的特殊字串理解安全文字寫入,不把「看起來正常」當成安全證據。
    修改位置
    checkpoint/files/main.js 的 card 寫入行
    先用 Elements 比較 childElementCount,再把 card.innerHTML 改成 card.textContent
    預期結果
    第一筆的 <strong> 標記會以文字顯示,li 不會多出 strong 子元素
    驗收證據
    Elements 的 childElementCount、innerHTMLtextContent 前後比較。
    卡住先查
    只看 renderServices 內 card 的那一行,不要先修改資料或 HTML
  6. 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 入口畫面、Elementsli/h2/p 階層、status 文字與 Console 無錯誤。
    卡住先查
    先確認目前 URL 是 examples/faq-variant.html,再對照 faq-list 是否和 querySelector 相同。
操作層級Live Lab 段落導覽:切換六個實驗步驟

讀碼順序:先找入口,再用畫面、ElementsConsole 與檔案行號驗證;本堂重點是資料如何變成 DOM,不用硬編碼通過訊息取代實際結果。

互動問答

先回答目前這一題,再展開解析;完成後按下一題,讓每一次回答都回到檔案或瀏覽器完成驗證。

Q1概念理解 DOMDocument 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-listrole=status 與 aria-live=polite,並讓 id 和後續 selector 完全一致。;觀察:ElementsDOM 階層、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 是否各出現一次。

立即驗證:

實際操作後檢查 ElementsDOM 階層、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 入口畫面、Elementsli/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 definedlist is null 或函式沒有被呼叫。
li 數量一直增加Elements 的子節點數確認 renderServices 開頭是否先呼叫 replaceChildren()
<strong> 變成粗體標籤Elements 的 DOM 子節點比較 innerHTMLtextContent,確認資料寫入方法。
Console 顯示 nullHTML 的 id 與 querySelector逐字比對 #service-list#status,再確認 script 使用 defer

常見誤解

本堂任務與驗收

完成依資料數量產生服務清單的 renderServices 函式,理解 DOM 節點建立與安全文字寫入,不把資料當成 HTML 執行。

官方延伸閱讀

下載講師版 Markdown:需要授課或離線查閱時使用,內容包含完整教學節奏。