JAVASCRIPT V2 · BROWSER · 41
DOM / Event / Render:JavaScript 怎麼真的改變畫面?
前面你已經會處理 value、array、object 與 function。現在第一次把這些 state 接到 Browser UI。這一課的核心不是背 querySelector,而是建立一條可以反覆使用的模型:user event → callback → state transition → render → DOM → screen。
Learning outcomes
- 區分 HTML source、live DOM 與畫面像素。
- 能追蹤 selector 回傳 element 或
null。 - 理解 event listener 是「登記 callback」,不是立刻執行。
- 能把 form input 正規化成 JavaScript state,再 render。
- 能用 Console / Elements evidence 定位 event、state、render 或 selector 問題。
1. Why now:JavaScript 的 value 終於要離開 Console
如果 Grade Tracker 只會在 Console 印出學生資料,它還不是一個使用者能操作的 app。Browser 程式要建立雙向資料流:
User input
↓
Browser event
↓
JavaScript state
↓
render()
↓
DOM
↓
screen
這條路徑會一路延伸到 React、Vue、Android UI,甚至桌面應用。框架只是把相同問題抽象化。
2. HTML source ≠ live DOM
<ul id="students"></ul>
Browser parse HTML 後建立 DOM objects。JavaScript 改的是目前 document 的 DOM state,不是把硬碟上的 HTML 檔案直接改掉。
const list =
document.querySelector("#students");
list.textContent = "Loaded";
3. Selector 是一個查詢,不是一張保證書
const saveButton =
document.querySelector("#save");
console.log(saveButton);
querySelector() 找不到時回 null。所以:
document
.querySelector("#missing")
.textContent = "Saved";
// TypeError:
// cannot read properties of null
第一個 debugging 問題不是「Browser 壞了嗎」,而是 selector 是否真的命中 live DOM。
4. Worked Example A:從 state render 一個值
const state = {
name: "Anson",
score: 90
};
function render() {
document.querySelector("#name")
.textContent = state.name;
document.querySelector("#score")
.textContent = state.score;
}
render();
這裡 state 是 canonical JavaScript data;DOM 是 state 的一種呈現。之後如果 score 變成 95,應該先更新 state,再 render。
5. Event listener:初始化時登記,事件發生後才執行
const button =
document.querySelector("#save");
button.addEventListener(
"click",
event => {
console.log(
"clicked",
event.type
);
}
);
| 時間 | 發生什麼 |
|---|---|
| script 初始化 | callback 被註冊給 Browser event system |
| 等待中 | callback 沒有一直執行 |
| 使用者 click | Browser 建立 event object |
| 事件 dispatch | 對應 callback 才執行 |
6. Worked Example B:Button → State → Render
const state = {
saved: false
};
const status =
document.querySelector("#status");
const save =
document.querySelector("#save");
function render() {
status.textContent =
state.saved
? "Saved"
: "Not saved";
status.classList.toggle(
"saved",
state.saved
);
}
save.addEventListener(
"click",
() => {
state.saved = true;
render();
}
);
render();
這就是本頁 mastery lab 真正在驗的能力:不是 source 裡「有 click 字樣」,而是 click 後 DOM 的 observable behavior 真的改變。
7. Form boundary:HTML input value 進 JavaScript 時仍要 normalize
form.addEventListener(
"submit",
event => {
event.preventDefault();
const name =
nameInput.value.trim();
const score =
Number(scoreInput.value);
console.log({
name,
score,
scoreType: typeof score
});
}
);
input.value 是 string。第 36 / 57 課的 type normalization 在 Browser 這裡真正派上用場。
8. Worked Example C:Array state → List DOM
const students = [];
function render() {
list.innerHTML =
students
.map(student => `
<li>
${student.name}
— ${student.score}
</li>
`)
.join("");
}
第 58 課的 map() 現在不只是 array 題目,而是把 data model transform 成 UI representation。
9. State ownership:不要讓 DOM 成為唯一資料庫
// fragile idea
const score =
Number(
document
.querySelector("#score")
.textContent
);
如果每次 business logic 都反向從畫面讀資料,你會很難知道真正 state 在哪。初學階段先採簡單原則:domain data 放 JavaScript state;DOM 主要負責呈現與收 input。
10. Common mistakes
A. Script 太早執行,selector 得到 null
確認 script 載入時機、defer、DOM 是否已存在。
B. 忘記 preventDefault
Form submit 造成 navigation/reload,看起來像 state「莫名消失」。
C. State 更新了但沒有 render
Console 看得到新 array,畫面卻沒變。
D. Render 只 append、不重建或去重
每次操作都把同樣內容重複加進 DOM。
11. Debug evidence:依資料流找第一個斷點
console.log("event fired");
console.log({ name, score });
console.log(students);
console.log(list);
- Event callback 有沒有進?
- Input value/type 正不正確?
- JavaScript state 有沒有更新?
render()有沒有被呼叫?- Selector 是否拿到 element?
- DOM 有改但 CSS 隱藏嗎?
12. Guided exercise:Predict → Click → Observe
let count = 0;
button.addEventListener(
"click",
() => {
count += 1;
output.textContent = count;
}
);
先回答:初始化後 output 是什麼?第一次 click 後 count / DOM 各是多少?第三次 click 後呢?再用 DevTools Console 與 Elements 驗證。
13. Independent exercise:完成 Save State
建立 #status 與 #save。初始顯示 Not saved;click 後顯示 Saved 並加上 saved class。不要用 inline onclick,使用 addEventListener。
14. Challenge:加入可重複新增的 Student Form
把 name/score form 接到 students array。要求:trim name、score 轉 number、拒絕空 name、submit 後 render list、reset form。思考哪一段是 input boundary,哪一段是 domain state,哪一段是 presentation。
Project checkpoint:Grade Tracker Browser Shell
Form
↓ submit
normalize input
↓
Student object
↓ push
students[]
↓
render()
↓
DOM list
完成這個 checkpoint 後,你已經有一個純 Browser app。下一步不是立刻上框架,而是把資料來源從 local memory 接到 HTTP/API。
15. Vocabulary / Summary
- DOM:Browser 對 document 的 live object model。
- selector:從 DOM 查詢 element 的規則。
- event:Browser/使用者造成的可觀察事件。
- listener/callback:事件發生後執行的 function。
- render:把 state 投影成 UI。
- state ownership:哪裡才是資料的 canonical source。
16. Further reading
Knowledge check
- 解釋 HTML source 與 live DOM 的差別。
- 說明
querySelector()為何可能回 null。 - 畫出 event → state → render → DOM。
- 解釋 input.value 為何仍需要 type normalization。
- 完成本頁 Live DOM mastery checks。