這一課完成後:你會知道 HTML 和 DOM 不是完全同一件事,能使用 querySelector / querySelectorAll 找到元素,修改文字、class、attribute,並從 JavaScript 資料建立新的 elements。
先把 JavaScript 正式接進頁面
沿用前幾課的 web-basics-playground,確認根目錄至少有:
web-basics-playground/
├─ index.html
├─ styles.css
└─ script.js
在 index.html 的 <head> 裡加入:
<script src="script.js" defer></script>
src 指向 JavaScript 檔案;defer 則告訴瀏覽器:先繼續解析 HTML,等文件結構準備好之後再執行這支 script。
這可以避免初學者很常遇到的問題:JavaScript 執行時,想找的 HTML 元素其實還沒被瀏覽器建立。
HTML 文件進到瀏覽器後,不只是原始文字
假設 HTML 有:
<main>
<h1>Web Basics</h1>
<p class="intro">我正在學 DOM。</p>
</main>
瀏覽器解析後,會建立類似樹狀結構:
└─ html
└─ body
└─ main
├─ h1
└─ p.intro
這個可由程式操作的文件模型,就是 DOM — Document Object Model。
HTML 是來源文件;DOM 是瀏覽器解析後建立、目前正在記憶體中存在的文件結構。JavaScript 操作的主要是 DOM。
document 是你進入 DOM 的入口
打開瀏覽器 DevTools Console,輸入:
document
你會看到目前頁面的 document object。
也可以試:
document.title
它會讀出目前頁面的 <title>。
再試:
document.title = "DOM Practice";
瀏覽器分頁標題會立刻改變。這就是第一次直接用 JavaScript 修改目前 DOM 狀態。
重新整理後,如果原始 HTML 沒改,分頁標題通常會恢復。因為你剛才改的是瀏覽器目前記憶體中的 DOM,不是硬碟上的 index.html。
先找到一個元素:querySelector
在 HTML 放一個:
<h1 id="page-title">我的第一個網站</h1>
<p class="intro">這段內容等等會被 JavaScript 找到。</p>
在 script.js:
const title = document.querySelector("#page-title");
const intro = document.querySelector(".intro");
console.log(title);
console.log(intro);
querySelector() 使用的就是你 CSS 已經看過的 selector 語法。
.intro → class selector
h1 → element selector
它只會回傳第一個符合條件的元素;如果完全找不到,結果會是 null。
先確認你真的找到東西,再往下改
這種程式:
const message = document.querySelector(".message");
console.log(message);
如果 Console 印出 null,通常代表 selector 寫錯、HTML 沒有這個元素,或 script 執行時機不對。
不要看到 Cannot set properties of null 就亂改 JavaScript。先確認「我要操作的 element 到底有沒有被選到」。
讀取與修改文字:textContent
const title = document.querySelector("#page-title");
console.log(title.textContent);
title.textContent = "JavaScript 已經找到這個標題";
textContent 可以讀取或設定元素內的文字內容。
如果只是要放純文字,優先使用它。它不會把字串當成 HTML 解析,因此對來自使用者或 API 的文字也比較安全。
innerHTML 可以放 HTML,但不要把它當預設選項
你可能會看到:
element.innerHTML = "<strong>Hello</strong>";
這會把字串解析成 HTML。它確實方便,但如果內容裡混入不可信資料,就可能造成安全問題。
要建立 DOM 結構 → 優先 createElement
innerHTML → 只有你清楚知道內容來源與風險時再用
這門課不會把 innerHTML 當成「快速把所有東西塞進頁面」的方法。
修改 CSS class:classList
先在 styles.css 加:
.highlight {
background: #fff4bf;
border-radius: 12px;
padding: 12px;
}
JavaScript:
const intro = document.querySelector(".intro");
intro.classList.add("highlight");
也可以:
intro.classList.remove("highlight");
intro.classList.toggle("highlight");
intro.classList.contains("highlight");
這比直接在 JavaScript 裡一條一條寫 CSS property 更容易維護。
Attribute 也可以被 JavaScript 操作
HTML:
<a id="project-link" href="https://example.com">專案連結</a>
JavaScript:
const link = document.querySelector("#project-link");
console.log(link.getAttribute("href"));
link.setAttribute("href", "https://github.com/");
link.setAttribute("target", "_blank");
很多常見 attribute 也可以直接透過 property 操作,例如:
link.href = "https://github.com/";
現在不用背「哪個一定要用哪種」。先知道 DOM element 是 object,而它的 attributes / properties 都可以被程式讀寫。
一次找多個元素:querySelectorAll
假設 HTML:
<ul>
<li class="skill">HTML</li>
<li class="skill">CSS</li>
<li class="skill">JavaScript</li>
</ul>
JavaScript:
const skills = document.querySelectorAll(".skill");
console.log(skills);
querySelectorAll() 會回傳一組符合的節點,常見型態是 NodeList。
你可以直接:
for (const skill of skills) {
console.log(skill.textContent);
}
上一課學的 loop,現在第一次真的走進頁面。
讓每一個 element 都根據資料改變
const skills = document.querySelectorAll(".skill");
for (const skill of skills) {
skill.textContent = `正在學:${skill.textContent}`;
}
這就是很典型的前端工作:先選到一組 DOM elements,再依照資料與規則改變它們。
從 JavaScript 建立新的元素
HTML 先放一個容器:
<ul id="lesson-list"></ul>
JavaScript:
const list = document.querySelector("#lesson-list");
const item = document.createElement("li");
item.textContent = "DOM";
list.append(item);
拆開來看:
textContent → 填內容
append → 把它放進 DOM
在 append 之前,這個 li 已經存在於 JavaScript,但還沒有出現在頁面樹上。
把上一課的 Array + Object 真正渲染到頁面
沿用資料:
const lessons = [
{ title: "HTML", done: true },
{ title: "CSS", done: true },
{ title: "JavaScript", done: true },
{ title: "DOM", done: false }
];
再把資料轉成 DOM:
const list = document.querySelector("#lesson-list");
for (const lesson of lessons) {
const item = document.createElement("li");
item.textContent = lesson.done
? `${lesson.title} ✓`
: `${lesson.title} — 尚未完成`;
if (lesson.done) {
item.classList.add("done");
}
list.append(item);
}
這裡已經把前一課的多個概念串起來:
這就是之後前端「拿資料產生畫面」的最小版本。
刪除元素
如果要把一個 element 從 DOM 移除:
const oldMessage = document.querySelector(".old-message");
if (oldMessage) {
oldMessage.remove();
}
這裡先加 if 是因為 selector 有可能找不到元素。上一課的條件判斷又派上用場。
不要每次都重新 query 同一個元素
如果同一段邏輯會重複操作某個 element,通常先存進變數:
const status = document.querySelector("#status");
status.textContent = "Loading...";
status.classList.add("active");
比這樣清楚:
document.querySelector("#status").textContent = "Loading...";
document.querySelector("#status").classList.add("active");
不只是少打一點字,也是在表達:「後面這幾個動作操作的是同一個 element。」
DOM 修改不等於修改原始 HTML 檔
這件事要再強調一次。
JavaScript 改了畫面,不代表它拿 VS Code 裡的 index.html 幫你重寫。
重新整理時,瀏覽器會重新讀檔,再重新建立 DOM,然後重新執行 JavaScript。
DevTools 的 Elements 面板現在會非常重要
開啟 DevTools → Elements。你看到的是目前的 DOM,不一定只是原始 HTML 的原封不動複製。
執行 JavaScript 後,如果新增了一個 li、class 被改變、attribute 被替換,你都能在 Elements 面板直接看到。
Elements → 看目前 DOM 與 CSS
小挑戰:把課程資料真正畫到頁面
不要先做按鈕。這一關只測「資料 → DOM」。
需求 1:HTML 中建立 #course-title、#progress、#lesson-list 三個位置。
需求 2:JavaScript 建立一個 course object,至少有 title 與 lessons。
需求 3:lessons 至少有 4 個 objects,每個有 title、done。
需求 4:使用 querySelector 找到標題與進度位置。
需求 5:根據資料計算完成數,顯示例如「3 / 4 completed」。
需求 6:使用 createElement 與 append 建立每一筆 lesson。
需求 7:完成的 lesson 加上一個 CSS class,不要用 inline style。
需求 8:DevTools Elements 中確認這些 li 真的是 JavaScript 執行後才加入 DOM。
這一課先不要做事件
你現在已經會讓 JavaScript 主動改畫面,但所有動作都是「頁面載入後程式自己執行」。
真正的網站還需要回答另一個問題:
這就是下一課的 Events。
最後收斂:DOM 是 HTML 和 JavaScript 的橋
JavaScript → 選取 DOM → 讀取 / 修改 / 建立 / 移除
CSS → 根據 element / class 決定畫面樣式
到這裡,前三種技術第一次真正接在一起了。但目前使用者還沒有控制權;下一課會加入事件,讓程式從「載入就執行」變成「使用者做了某件事才執行」。
完成條件:你能從 JavaScript 選取 DOM elements、讀寫文字與 attributes、使用 classList、走訪多個 elements,並把 array/object 資料透過 createElement 渲染成頁面內容。