[基礎課程] JavaScript 教學(二):BOM 與 DOM

本篇將深入介紹 JavaScript 中最重要的兩個概念:BOM(Browser Object Model,瀏覽器物件模型)與 DOM(Document Object Model,文件物件模型)。這兩個模型是 JavaScript 與網頁互動的基礎,掌握它們將讓您能夠動態控制網頁內容和瀏覽器行為。

什麼是 BOM 與 DOM?

在開始學習具體操作之前,我們需要先理解這兩個模型的基本概念:

BOM(Browser Object Model)

BOM 是瀏覽器物件模型,它提供了 JavaScript 與瀏覽器本身互動的介面。透過 BOM,我們可以:

  • 控制瀏覽器視窗
  • 操作瀏覽器歷史記錄
  • 獲取螢幕資訊
  • 控制 URL 導向

DOM(Document Object Model)

DOM 是文件物件模型,它將 HTML 文件結構化為一個樹狀結構,讓 JavaScript 能夠:

  • 存取和修改 HTML 元素
  • 改變元素的樣式和內容
  • 響應用戶互動事件

graph TD
subgraph BOM["瀏覽器物件模型 (BOM)"]
    Window["window 物件"]
    Window --> History["history 物件"]
    Window --> Location["location 物件"]
    Window --> Navigator["navigator 物件"]
    Window --> Screen["screen 物件"]
    Window --> Document["document 物件"]
end

subgraph DOM["文件物件模型 (DOM)"]
    Document --> HTML["html 元素"]
    HTML --> Head["head 元素"]
    HTML --> Body["body 元素"]
    Head --> Title["title<br/>網頁標題"]
    Body --> H1["h1<br/>主標題"]
    Body --> Div["div#container"]
    Div --> P["p<br/>段落文字"]
    Div --> Button["button<br/>按鈕"]
end

style BOM fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
style DOM fill:#f3e5f5,stroke:#7b1fa2,stroke-width:2px

兩者的關係

  • BOM 是瀏覽器的整體模型,window 物件代表瀏覽器視窗本身,是 BOM 的根物件,包含多項模型物件以及所有全域物件、函式和變數。BOM 提供了 JavaScript 與瀏覽器溝通的介面。
  • DOM 是 BOM 的一部分,document 物件代表整個 HTML 文件,它是 DOM 的根物件。透過 DOM,我們可以存取和操作網頁中的所有 HTML 元素、屬性和內容。DOM 將 HTML 文件視為一個樹狀結構,每個元素都是樹中的一個節點,這使得我們能夠輕鬆地遍歷、修改和管理網頁內容
  • 我們應該透過 window.document 來存取 DOM,但因為 window 是所有的 root,所以也可以直接使用 document 來存取

重要觀念:

  • BOM 控制瀏覽器行為(如開啟新視窗、導向頁面)
  • DOM 控制網頁內容(如修改文字、改變樣式)
  • 兩者配合使用,可以創造豐富的網頁互動體驗

BOM 基礎操作

瀏覽器物件模型(BOM)提供了豐富的操作介面,讓我們能夠控制瀏覽器的各種行為和功能。以下我們將介紹一些最常用且實用的 BOM 操作方法,這些方法可以幫助我們更好地掌握瀏覽器的互動能力。

window 物件

window 是 BOM 的根物件,代表整個瀏覽器視窗。在瀏覽器環境中,全域變數和函數都屬於 window 物件。

// 以下兩種寫法效果相同
console.log("Hello World");
window.console.log("Hello World");

// 全域變數實際上是 window 的屬性
let globalVar = "全域變數";
console.log(window.globalVar); // "全域變數"

window 物件還提供了許多實用的方法。

方法 描述 用途
window.scrollTo() 滾動到指定位置 頁面導航、錨點跳轉
window.scrollBy() 相對滾動 平滑滾動效果
window.print() 列印當前頁面 列印功能
window.focus() 讓視窗獲得焦點 視窗管理
window.blur() 讓視窗失去焦點 視窗管理
window.moveTo() 移動視窗位置 視窗定位
window.resizeTo() 調整視窗大小 視窗尺寸控制
window.innerWidth 視窗內部寬度 響應式設計
window.innerHeight 視窗內部高度 響應式設計
window.outerWidth 視窗外部寬度 視窗管理
window.outerHeight 視窗外部高度 視窗管理

對話框 alert、confirm、prompt

請在瀏覽器控制台中逐一執行下述程式碼,觀察不同對話框的效果。

// 基本提示框
alert("這是一個提示訊息");

// 確認對話框 - 回傳 true 或 false
let result = confirm("您確定要刪除這個項目嗎?");
if (result) {
console.log("使用者選擇了確定");
} else {
console.log("使用者選擇了取消");
}

// 輸入對話框 - 回傳使用者輸入的字串
let name = prompt("請輸入您的姓名:", "預設值");
if (name) {
console.log("您好," + name);
}

計時器 setTimeout、setInterval

計時器是 JavaScript 中非常重要的功能,它允許我們在指定的時間後執行程式碼,或者重複執行某些操作。這在製作動畫、自動更新內容等場景中非常有用。

// setTimeout - 延遲執行
let timeoutId = setTimeout(function() {
console.log("3 秒後執行");
}, 3000);

// clearTimeout - 取消延遲執行
clearTimeout(timeoutId);

// setInterval - 重複執行
let counter = 0;
let intervalId = setInterval(function() {
counter++;
console.log("計數:" + counter);

if (counter >= 5) {
clearInterval(intervalId); // 停止計時器
console.log("計時器已停止");
}
}, 1000);

計時器 ID 機制:

  • setTimeoutsetInterval 都會回傳一個數字 ID
  • 這個 ID 是由瀏覽器提供的唯一標記
  • 使用 clearTimeout(ID)clearInterval(ID) 可以取消對應的計時器
  • 即使計時器已經執行完成,清除操作也不會出錯

視窗控制 open

視窗控制功能允許我們程式化地操作瀏覽器視窗,包括開啟新視窗、調整視窗大小等。不過需要注意的是,現代瀏覽器基於安全考量可能對這些功能有所限制。

// 開啟新視窗
let newWindow = window.open("https://www.google.com", "_blank", "width=500,height=400");

// 關閉視窗
newWindow.close(); // 只能在同源視窗中使用

// 調整視窗大小
window.resizeTo(800, 600); // 現代瀏覽器可能限制此功能

注意事項:

  • 基於安全考量,現代瀏覽器對某些 BOM 操作可能有限制
  • 彈出視窗可能被瀏覽器阻擋
  • 某些方法需要用戶互動才能執行

location 物件

BOM 中處理 URL 和頁面導向的重要工具。它包含了當前頁面的完整 URL 資訊,並提供了豐富的方法來操作和導向頁面。無論是獲取當前頁面資訊還是進行頁面跳轉,location 物件都是不可或缺的。

// 獲取當前 URL
console.log(location.href);

// 導向到新頁面 - 方法一:直接設定 href
location.href = "https://www.google.com";

// 導向到新頁面 - 方法二:使用 assign() 方法
location.assign("https://www.google.com");

// 導向到新頁面 - 方法三:使用 replace() 方法(替換當前頁面)
location.replace("https://www.google.com");

// 重新載入頁面
location.reload();

// 獲取 URL 各部分
console.log("協議:" + location.protocol); // https:
console.log("主機:" + location.host); // www.example.com
console.log("路徑:" + location.pathname); // /page.html

location.href vs location.assign() vs location.replace():

  • location.href = "URL":直接設定 href 屬性
  • location.assign("URL"):使用 assign() 方法導向
  • location.replace("URL"):替換當前頁面,不新增歷史記錄
  • assign()href 都會在歷史記錄中新增一筆記錄
  • replace() 會替換當前歷史記錄,無法使用「上一頁」回到原頁面

實際應用場景:

  • 登入後導向:使用 assign()href,讓用戶可以回到登入頁面
  • 錯誤頁面導向:使用 replace(),避免用戶回到錯誤狀態
  • 表單提交後:通常使用 replace() 防止重複提交

location 物件還有一些實用的屬性。

屬性 描述 用途
location.search URL 查詢參數 參數解析
location.hash URL 錨點部分 單頁應用路由
location.origin 協議+主機+端口 同源檢測
location.port 端口號 環境檢測
location.hostname 主機名稱 域名檢測

screen 物件

screen 物件提供了關於用戶螢幕的詳細資訊,包括螢幕尺寸、色彩深度、像素密度等。這些資訊在響應式設計、適配不同設備和優化用戶體驗時非常重要。

// 螢幕尺寸資訊
console.log("螢幕寬度:" + screen.width); // 螢幕實際寬度
console.log("螢幕高度:" + screen.height); // 螢幕實際高度
console.log("可用寬度:" + screen.availWidth); // 可用螢幕寬度(排除工作列)
console.log("可用高度:" + screen.availHeight); // 可用螢幕高度(排除工作列)

// 色彩資訊
console.log("色彩深度:" + screen.colorDepth); // 色彩深度(位元)
console.log("像素深度:" + screen.pixelDepth); // 像素深度

// 方向資訊
console.log("螢幕方向:" + screen.orientation.type); // 螢幕方向(landscape/portrait)

screen 物件的應用場景:

  • 響應式設計:根據螢幕尺寸調整佈局
  • 設備適配:為不同設備提供最佳體驗
  • 性能優化:根據螢幕能力調整內容品質
  • 用戶體驗:避免內容超出螢幕範圍

screen 物件還有一些實用的屬性。

屬性 描述 用途
screen.availLeft 可用區域左邊距 視窗定位
screen.availTop 可用區域上邊距 視窗定位
screen.colorDepth 色彩深度 圖片品質
screen.pixelDepth 像素深度 顯示品質
screen.orientation 螢幕方向 適配設計

history 物件

能夠程式化地控制瀏覽器的前進、後退功能。這在製作單頁應用程式(SPA)或需要自訂導航行為的網頁中非常有用。透過 history 物件,我們可以實現更靈活的頁面導航體驗。

// 回到上一頁
history.back();

// 前往下一頁
history.forward();

// 跳轉指定頁數(正數前進,負數後退)
history.go(-2); // 回到前兩頁

history 物件提供更多導航控制功能。

方法 描述 用途
history.pushState() 新增歷史記錄 單頁應用路由
history.replaceState() 替換歷史記錄 路由狀態管理
history.length 歷史記錄數量 導航狀態檢測
history.state 當前狀態 狀態管理

其他 BOM 物件與方法

除了前面介紹的核心物件外,BOM 還提供了許多其他有用的物件和方法。這些物件在特定場景下非常有用,值得了解它們的存在和基本用途。

Console API

方法 描述 用途
console.log() 一般日誌 除錯輸出
console.warn() 警告訊息 警告輸出
console.error() 錯誤訊息 錯誤輸出
console.table() 表格輸出 資料展示
console.group() 分組輸出 日誌組織
console.time() 計時開始 性能測量
console.timeEnd() 計時結束 性能測量

提供瀏覽器和系統的詳細資訊,常用於檢測瀏覽器類型和功能支援。

屬性/方法 描述 用途
navigator.userAgent 瀏覽器用戶代理字串 檢測瀏覽器類型和版本
navigator.language 用戶偏好語言 國際化應用
navigator.onLine 網路連線狀態 離線功能檢測
navigator.geolocation 地理位置 API 位置服務
navigator.cookieEnabled Cookie 啟用狀態 功能檢測
navigator.platform 作業系統平台 平台適配
navigator.vendor 瀏覽器廠商 瀏覽器檢測
navigator.appName 瀏覽器名稱 相容性檢測
navigator.appVersion 瀏覽器版本 版本檢測

localStorage 與 sessionStorage

提供客戶端儲存功能,用於保存用戶資料和應用狀態。

儲存類型 生命週期 用途
localStorage 永久保存 用戶偏好設定、應用狀態
sessionStorage 會話期間 臨時資料、表單狀態

常用方法:

  • setItem(key, value) - 儲存資料
  • getItem(key) - 讀取資料
  • removeItem(key) - 刪除資料
  • clear() - 清除所有資料
demo.js
// localStorage 範例
// ----------------------------------------------------------------
// 儲存用戶偏好設定
localStorage.setItem('theme', 'dark');
localStorage.setItem('language', 'zh-TW');

// 讀取儲存的資料
console.log('主題:', localStorage.getItem('theme')); // 主題:dark
console.log('語言:', localStorage.getItem('language')); // 語言:zh-TW

// 檢查儲存空間是否可用,或者儲存情況都可能拋出異常
function checkStorageSupport() {
try {
localStorage.setItem('test', 'test');
localStorage.removeItem('test');

// 以下情況可能會拋出異常:
// 1. 儲存空間已滿 (超過 5-10MB 限制)
// 2. 瀏覽器隱私模式下無法使用 localStorage
// 3. 用戶禁用了 Web Storage 功能
// 4. 存取權限被拒絕
// 5. 儲存的值不是合法的字串格式
return true; // 支援 localStorage
} catch (e) {
console.log('localStorage 不可用:', e.message);
return false; // 不支援 localStorage
}
}

// 清除特定資料
localStorage.removeItem('theme');

// sessionStorage 範例
// ----------------------------------------------------------------
// 儲存表單狀態
sessionStorage.setItem('formData', JSON.stringify({
name: '張三',
email: 'zhang@example.com',
message: '這是一個測試訊息'
}));

// 讀取表單資料
const formData = JSON.parse(sessionStorage.getItem('formData'));
console.log('表單資料:', formData);

// 清除特定資料
sessionStorage.clear(); // 清除所有 sessionStorage 資料

小技巧:

  • localStorage 資料會永久保存,除非手動刪除
  • sessionStorage 資料在關閉分頁後會自動清除
  • 儲存容量限制約為 5-10MB
  • 只能儲存字串,物件需要先轉換為 JSON

Performance API

方法/屬性 描述 用途
performance.now() 高精度時間戳 性能測量
performance.memory 記憶體使用 性能監控
performance.timing 頁面載入時間 性能分析

使用建議:

  • 這些物件和方法在特定場景下非常有用
  • 建議根據實際需求選擇合適的 API
  • 注意瀏覽器相容性和安全限制
  • 優先使用現代標準 API
  • 常用方法建議熟記,不常用的可以查閱文件

DOM 基礎概念

DOM 將 HTML 文件表示為一個樹狀結構,每個 HTML 元素都是一個節點(Node)。透過這個結構,JavaScript 可以精確地找到並操作任何元素。

DOM 樹狀結構

<!DOCTYPE html>
<html>
<head>
<title>網頁標題</title>
</head>
<body>
<h1>主標題</h1>
<div id="container">
<p>段落文字</p>
<button>按鈕</button>
</div>
</body>
</html>

對應的 DOM 樹狀結構:


graph TD
Document["document"]
Document --> HTML["html"]
HTML --> Head["head"]
HTML --> Body["body"]
Head --> Title["title<br/>網頁標題"]
Body --> H1["h1<br/>主標題"]
Body --> Div["div#container"]
Div --> P["p<br/>段落文字"]
Div --> Button["button<br/>按鈕"]

style Document fill:#e8f5e8,stroke:#2e7d32,stroke-width:3px
style HTML fill:#fff3e0,stroke:#f57c00,stroke-width:2px
style Body fill:#fce4ec,stroke:#c2185b,stroke-width:2px

節點類型

DOM 中有多種節點類型:

  • 元素節點:HTML 標籤(如 <div><p>
  • 文字節點:標籤內的文字內容
  • 屬性節點:HTML 屬性(如 idclass

DOM 元素選擇

在 DOM 操作中,第一步就是要找到我們想要操作的元素。JavaScript 提供了多種方法來選擇元素,每種方法都有其特定的使用場景和優缺點。掌握這些選擇方法是進行 DOM 操作的基礎。

getElementById

<div id="myDiv">這是一個 div</div>
// 透過 ID 選擇元素(回傳單一元素)
let element = document.getElementById("myDiv");
console.log(element); // <div id="myDiv">這是一個 div</div>

素材準備:

dom-basic.html
<!DOCTYPE html>
<html>
<head>
<title>DOM 基礎練習</title>
</head>
<body>
<h1 id="title">我的網頁</h1>
<div id="container">
<p class="text">第一段文字</p>
<p class="text">第二段文字</p>
<button id="btn">點擊我</button>
</div>

<script>
// 在這裡練習 DOM 操作
</script>
</body>
</html>

getElementsByTagName

// 選擇所有 p 標籤(回傳 HTMLCollection)
let paragraphs = document.getElementsByTagName("p");
console.log(paragraphs.length); // 2
console.log(paragraphs[0]); // 第一個 p 元素

getElementsByClassName

// 選擇所有 class 為 "text" 的元素(回傳 HTMLCollection)
let textElements = document.getElementsByClassName("text");
console.log(textElements.length); // 2

getElementsByName

<input type="text" name="username" value="使用者名稱">
// 選擇所有 name 為 "username" 的元素
let inputs = document.getElementsByName("username");
console.log(inputs[0].value); // "使用者名稱"

querySelector (推薦)

除了傳統的選擇方法,現代瀏覽器還支援更強大和靈活的 querySelector 方法。這些方法使用 CSS 選擇器語法,讓元素選擇變得更加直觀和強大。querySelector 系列方法是目前推薦使用的選擇方法。

// 選擇第一個符合條件的元素 (返回 Element 物件)
let firstP = document.querySelector("p");
let title = document.querySelector("#title");
let textClass = document.querySelector(".text");

// 選擇所有符合條件的元素(回傳 NodeList)
let allTexts = document.querySelectorAll(".text");
let allButtons = document.querySelectorAll("button");

小技巧:

  • querySelector 使用 CSS Selector 語法,只回傳第一個符合的元素
  • querySelectorAll 使用 CSS Selector 語法,回傳所有符合的元素

DOM 選擇器返回值差異

不同的 DOM 選擇方法會返回不同類型的物件,了解這些差異對於正確操作 DOM 元素非常重要。

返回值類型說明:

選擇方法 返回值類型 數量 更新機制 可用方法
getElementById Element 物件 或 null 單一 靜態 Element 方法
getElementsByTagName HTMLCollection 多個 動態 HTMLCollection 方法
getElementsByClassName HTMLCollection 多個 動態 HTMLCollection 方法
querySelector Element 物件 或 null 單一 靜態 Element 方法
querySelectorAll NodeList 多個 靜態 NodeList 方法

Element 物件

// 返回 Element 物件的方法
const element1 = document.getElementById('myId');
const element2 = document.querySelector('.myClass');

console.log(element1); // Element 物件或 null
console.log(element2); // Element 物件或 null

// 可以直接操作
if (element1) {
element1.textContent = '修改內容';
element1.style.color = 'red';
element1.addEventListener('click', function() {
console.log('點擊事件');
});
}

HTMLCollection

// 返回 HTMLCollection 的方法
const elements1 = document.getElementsByTagName('p');
const elements2 = document.getElementsByClassName('text');

console.log(elements1); // HTMLCollection 物件
console.log(elements2); // HTMLCollection 物件

// 特點:動態更新
console.log(elements1.length); // 假設有 2 個元素

// 新增元素後,HTMLCollection 會自動更新
const newP = document.createElement('p');
document.body.appendChild(newP);
console.log(elements1.length); // 自動變成 3 個

// 只能使用數字索引存取
for (let i = 0; i < elements1.length; i++) {
elements1[i].style.color = 'blue';
}

NodeList

// 返回 NodeList 的方法
const elements = document.querySelectorAll('p');

console.log(elements); // NodeList 物件

// 特點:靜態集合,不會自動更新
console.log(elements.length); // 假設有 2 個元素

// 新增元素後,NodeList 不會更新
const newP = document.createElement('p');
document.body.appendChild(newP);
console.log(elements.length); // 仍然是 2 個

// 可以使用 forEach 方法
elements.forEach(element => {
element.style.color = 'green';
});

// 也可以使用數字索引
for (let i = 0; i < elements.length; i++) {
elements[i].style.fontSize = '16px';
}

HTMLCollection vs NodeList 差異

// HTMLCollection - 動態更新範例
const dynamicElements = document.getElementsByTagName('div');
console.log('初始數量:', dynamicElements.length); // 假設 3 個

// 新增元素
const newDiv = document.createElement('div');
document.body.appendChild(newDiv);

console.log('新增後數量:', dynamicElements.length); // 自動變成 4 個

// NodeList - 靜態集合範例
const staticElements = document.querySelectorAll('div');
console.log('初始數量:', staticElements.length); // 假設 3 個

// 新增元素
const newDiv2 = document.createElement('div');
document.body.appendChild(newDiv2);

console.log('新增後數量:', staticElements.length); // 仍然是 3 個

// 需要重新查詢才能獲得更新後的結果
const updatedElements = document.querySelectorAll('div');
console.log('重新查詢後:', updatedElements.length); // 現在是 4 個

重要提醒:

  • HTMLCollection 是動態的,會自動反映 DOM 的變化
  • NodeList 是靜態的,DOM 變化後需要重新查詢
  • 在迴圈中修改 DOM 時要特別注意這個差異

最佳實踐:

  • 現代開發推薦使用 querySelector 系列方法
  • 需要動態更新時考慮使用 getElementsBy* 方法
  • 在複雜的 DOM 操作中,重新查詢比依賴動態更新更安全

使用 document 子物件

除了透過選擇方法存取元素外,document 物件還提供了一些快速直接存取的子物件,這些物件代表 HTML 文件中的重要結構元素。

document. 子物件 描述
document.head head 元素,但唯獨屬性不可修改
document.body body 元素,操作例如document.body.style.backgroundColor="red";
document.forms 文件內的全部表單元素之通用集合
document.links 文件內的全部具備 href 之元素之通用集合
document.images 文件內的全部 img 元素之通用集合
document.scripts 文件內的全部 script 元素之通用集合

返回值類型說明:

document. 子物件 返回值類型 特點
document.head Element 物件 單一元素,可直接操作
document.body Element 物件 單一元素,可直接操作
document.forms HTMLCollection 動態集合,會自動更新
document.links HTMLCollection 動態集合,會自動更新
document.images HTMLCollection 動態集合,會自動更新
document.scripts HTMLCollection 動態集合,會自動更新
// 直接存取 document 子物件
console.log(document.head); // head 元素
console.log(document.body); // body 元素

// 操作 body 樣式
document.body.style.backgroundColor = "lightblue";

// 存取所有表單
console.log(document.forms.length); // 表單數量
console.log(document.forms[0]); // 第一個表單

// 存取所有連結
console.log(document.links.length); // 連結數量
console.log(document.links[0].href); // 第一個連結的 href

// 存取所有圖片
console.log(document.images.length); // 圖片數量
console.log(document.images[0].src); // 第一張圖片的 src

// 存取所有腳本
console.log(document.scripts.length); // 腳本數量

document 子物件的特點:

  • 這些是 document 物件的直接屬性,無需使用選擇方法
  • 提供了快速存取常用元素的方式
  • document.headdocument.body 返回 Element 物件,可直接操作
  • document.formsdocument.linksdocument.imagesdocument.scripts 返回 HTMLCollection,具有動態更新特性
  • 在實際開發中非常實用

DOM 內容操作

找到元素後,下一步就是對元素進行操作。DOM 提供了豐富的方法來讀取和修改元素的內容、屬性和樣式。這些操作是動態網頁開發的核心,讓我們能夠創造豐富的用戶體驗。

內容操作

innerHTML vs textContent

兩種不同的內容操作方式,各有其特點和使用場景:

特性 innerHTML textContent
HTML 標籤 會解析並渲染 視為純文字顯示
執行效率 較慢(解析 HTML 渲染) 較快
安全性 需注意 XSS 風險 較安全
使用場景 需要 HTML 結構時 純文字內容時
// demo > 內容操作
// --------------------------------------------------
let element = document.getElementById("title");

// 讀取內容
console.log(element.innerHTML); // "我的網頁"
console.log(element.textContent); // "我的網頁"

// 修改內容
element.innerHTML = "<em>新的標題</em>"; // 支援 HTML 標籤
element.textContent = "純文字標題"; // 只支援純文字

// 動態添加內容
let container = document.getElementById("container");
container.innerHTML += "<p>新添加的段落</p>";

使用建議:

  • 需要動態插入 HTML 結構時使用 innerHTML
  • 只需要處理純文字時使用 textContent
  • 處理使用者輸入時優先使用 textContent 避免 XSS 風險

屬性操作

基本屬性操作

// demo > HTML 元素屬性
// --------------------------------------------------
let element = document.getElementById("title");

// 讀取屬性
console.log(element.id); // "title"
console.log(element.className); // 如果有 class 的話

// 設定屬性
element.className = "new-class";
element.setAttribute("data-custom", "自訂屬性值");

// 移除屬性
element.removeAttribute("data-custom");

使用 setAttribute 設定 style

除了直接操作 style 物件,也可以使用 setAttribute 方法來設定樣式:

// demo > 使用 setAttribute 設定 style
// --------------------------------------------------
let element = document.getElementById("title");

// 方法一:直接設定 style 屬性
element.setAttribute("style", "color: red; font-size: 24px; background-color: #f0f0f0;");

// 方法二:使用模板字串
element.setAttribute("style", `
color: blue;
font-size: 28px;
font-weight: bold;
text-align: center;
padding: 10px;
border: 2px solid #007bff;
border-radius: 5px;
`);

// 方法三:動態組合樣式
let color = "green";
let fontSize = "20px";
element.setAttribute("style", `color: ${color}; font-size: ${fontSize};`);

setAttribute vs style 物件:

  • setAttribute("style", "css字串") - 完全覆蓋所有樣式
  • element.style.property = value - 精確控制單一屬性
  • 選擇哪種方法取決於具體需求

樣式操作

樣式設定方法對比

DOM 提供了多種方式來設定元素的 CSS 樣式,每種方法都有其適用場景:

方法 特點 適用場景
逐個設定 精確控制,不會影響其他樣式 單一屬性修改
直接設定 style 完全覆蓋所有樣式 需要重新設定所有樣式
Object.assign() 批量設定,程式碼簡潔 需要設定多個相關樣式
cssText 一次性設定,效能較好 大量樣式設定
setProperty() 支援 CSS 變數和複雜值 CSS 變數和特殊屬性
setAttribute() 使用字串設定,靈活性高 動態組合樣式

逐個設定樣式

// demo > 逐個設定樣式
// --------------------------------------------------
let element = document.getElementById("title");

// 直接設定樣式
element.style.color = "red";
element.style.backgroundColor = "#f0f0f0";
element.style.fontSize = "24px";

// 讀取樣式
console.log(element.style.color); // "red"

批量設定樣式

// demo > 批量設定樣式
// --------------------------------------------------
let element = document.getElementById("title");

// 方法一:使用 Object.assign() 批量設定
Object.assign(element.style, {
color: "blue",
backgroundColor: "#e8f4fd",
fontSize: "28px",
fontWeight: "bold",
textAlign: "center",
padding: "10px",
border: "2px solid #007bff",
borderRadius: "5px"
});

// 方法二:使用 cssText 設定多個樣式
element.style.cssText = `
color: green;
background-color: #f8f9fa;
font-size: 32px;
font-weight: bold;
text-align: center;
padding: 15px;
border: 3px solid #28a745;
border-radius: 8px;
box-shadow: 0 4px 8px rgba(0,0,0,0.1);
`;

// 方法三:直接設定 style 屬性(會覆蓋所有現有樣式)
element.style = "color: purple; background-color: #f0f0f0; font-size: 24px; padding: 10px;";

進階樣式操作

// demo > 進階樣式操作
// --------------------------------------------------
let element = document.getElementById("title");

// 使用 setProperty() 設定 CSS 變數
element.style.setProperty('--custom-color', '#ff6b6b');
element.style.setProperty('--custom-size', '20px');

// 使用 setAttribute 動態設定樣式
let theme = "dark";
let styles = theme === "dark"
? "color: white; background-color: #333;"
: "color: black; background-color: #fff;";
element.setAttribute("style", styles);

重要提醒:

  • 直接設定 element.style = "css字串" 會覆蓋元素的所有現有樣式
  • 如果需要保留某些樣式,建議使用 cssTextObject.assign()
  • setAttribute("style", "css字串") 也會完全覆蓋現有樣式
  • 在複雜的樣式操作中,建議先備份原有樣式

最佳實踐:

  • 單一屬性修改使用 element.style.property = value
  • 多個相關樣式使用 Object.assign()cssText
  • 需要動態組合樣式時使用 setAttribute()
  • CSS 變數操作使用 setProperty()
  • 完全重新設定樣式時使用直接賦值或 setAttribute()

事件處理基礎

事件處理是 JavaScript 與用戶互動的核心機制。透過事件處理,我們可以響應用戶的各種操作,如點擊、滑鼠移動、鍵盤輸入等。掌握事件處理是製作互動網頁的關鍵技能。

什麼是事件?

事件是網頁中發生的動作,如點擊、滑鼠移動、鍵盤按下等。JavaScript 可以監聽這些事件並執行相應的程式碼。

常見事件類型

事件類型 事件名稱 描述
滑鼠事件 click 點擊元素
滑鼠事件 dblclick 雙擊元素
滑鼠事件 mouseover 滑鼠移入
滑鼠事件 mouseout 滑鼠移出
鍵盤事件 keydown 按下按鍵
鍵盤事件 keyup 放開按鍵
表單事件 change 內容改變
表單事件 submit 表單提交
瀏覽器事件 load 頁面載入完成

事件處理方法

JavaScript 提供了多種方式來綁定事件處理器,每種方法都有其優缺點。從簡單的 HTML 屬性到現代的 addEventListener 方法,我們需要根據具體需求選擇合適的方法。

方法一:HTML 屬性

<button onclick="showMessage()">點擊我</button>
<input type="text" onchange="handleChange(this.value)">

<script>
function showMessage() {
alert("按鈕被點擊了!");
}

function handleChange(value) {
console.log("輸入值改變為:" + value);
}
</script>

HTML 屬性方法的優缺點:
優點:

  • 簡單直接,容易理解
  • 適合簡單的互動功能

缺點:

  • HTML 和 JavaScript 混合,違反關注點分離原則
  • 只能綁定一個事件處理器
  • 難以維護和除錯
  • 不支援事件委派
  • 安全性較差(可能被 XSS 攻擊)

方法二:JavaScript 綁定

<button id="myButton">點擊我</button>
<input type="text" id="myInput">

<script>
// 獲取元素
let button = document.getElementById("myButton");
let input = document.getElementById("myInput");

// 綁定事件
button.onclick = function() {
alert("按鈕被點擊了!");
};

input.onchange = function() {
console.log("輸入值改變為:" + this.value);
};
</script>

JavaScript 綁定方法的優缺點:
優點:

  • 將 HTML 和 JavaScript 分離
  • 比 HTML 屬性更安全
  • 可以動態綁定和移除

缺點:

  • 只能綁定一個事件處理器(後面的會覆蓋前面的)
  • 無法綁定多個處理器
  • 不支援事件委派

方法三:addEventListener(推薦)

<button id="myButton">點擊我</button>

<script>
let button = document.getElementById("myButton");

// 使用 addEventListener 綁定事件
button.addEventListener("click", function() {
alert("按鈕被點擊了!");
});

// 可以綁定多個事件處理器
button.addEventListener("click", function() {
console.log("第二個事件處理器");
});
</script>

addEventListener 方法的優缺點:
優點:

  • 可以綁定多個事件處理器
  • 支援事件委派
  • 更好的錯誤處理
  • 可以控制事件捕獲和冒泡階段
  • 更容易維護和除錯
  • 符合現代 JavaScript 最佳實踐

缺點:

  • 語法稍微複雜
  • 需要記住正確的事件名稱(如 “click” 而不是 “onclick”)

當需要移除事件處理器時,必須使用 removeEventListener 方法:

<button id="myButton">點擊我</button>
<button id="removeBtn">移除事件</button>

<script>
let button = document.getElementById("myButton");
let removeBtn = document.getElementById("removeBtn");

// 定義事件處理器函數(必須是具名函數)
function handleClick() {
alert("按鈕被點擊了!");
}

// 綁定事件
button.addEventListener("click", handleClick);

// 移除事件處理器
removeBtn.addEventListener("click", function() {
button.removeEventListener("click", handleClick);
console.log("事件處理器已移除");
});
</script>

removeEventListener 重要注意事項:

  • 只能移除具名函數的處理器,匿名函數無法移除
  • 必須提供相同的函數參考和事件類型
  • 常用於清理資源、防止記憶體洩漏
  • 在組件卸載或頁面切換時特別重要

事件物件

事件處理器會自動接收一個事件物件,包含事件的詳細資訊:

button.addEventListener("click", function(event) {
console.log("事件類型:" + event.type);
console.log("目標元素:" + event.target);
console.log("滑鼠位置:" + event.clientX + ", " + event.clientY);
});

實作練習

理論學習固然重要,但真正的技能提升來自於實際操作。透過以下練習,我們將把前面學習的知識應用到實際場景中,幫助您更好地理解和掌握 BOM 與 DOM 的操作。

練習一:簡單的互動按鈕

素材準備:

interactive-button.html
<!DOCTYPE html>
<html>
<head>
<title>互動按鈕練習</title>
<style>
.button {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
margin: 10px;
}
.button:hover {
background-color: #0056b3;
}
</style>
</head>
<body>
<h1>互動按鈕練習</h1>
<button id="changeColorBtn" class="button">改變顏色</button>
<button id="changeTextBtn" class="button">改變文字</button>
<div id="displayArea" style="padding: 20px; border: 1px solid #ccc; margin: 20px;">
這裡是顯示區域
</div>

<script>
// 在這裡實作互動功能
</script>
</body>
</html>

實作要求:

  1. 點擊「改變顏色」按鈕時,隨機改變顯示區域的背景顏色
  2. 點擊「改變文字」按鈕時,改變顯示區域的文字內容
  3. 當滑鼠移入按鈕時,在控制台顯示訊息
// 實作程式碼
let changeColorBtn = document.getElementById("changeColorBtn");
let changeTextBtn = document.getElementById("changeTextBtn");
let displayArea = document.getElementById("displayArea");

// 顏色陣列
let colors = ["#ff6b6b", "#4ecdc4", "#45b7d1", "#96ceb4", "#feca57"];

// 改變顏色功能
changeColorBtn.addEventListener("click", function() {
let randomColor = colors[Math.floor(Math.random() * colors.length)];
displayArea.style.backgroundColor = randomColor;
console.log("背景顏色已改變為:" + randomColor);
});

// 改變文字功能
changeTextBtn.addEventListener("click", function() {
let texts = ["Hello World!", "JavaScript 很有趣!", "DOM 操作很簡單", "繼續學習吧!"];
let randomText = texts[Math.floor(Math.random() * texts.length)];
displayArea.textContent = randomText;
});

// 滑鼠移入事件
changeColorBtn.addEventListener("mouseover", function() {
console.log("滑鼠移入改變顏色按鈕");
});

changeTextBtn.addEventListener("mouseover", function() {
console.log("滑鼠移入改變文字按鈕");
});

練習二:表單驗證

表單驗證是網頁開發中非常常見的需求。透過這個練習,我們將學習如何結合事件處理和 DOM 操作來實現即時的表單驗證功能,這在實際的網頁應用中非常重要。

素材準備:

form-validation.html
<!DOCTYPE html>
<html>
<head>
<title>表單驗證練習</title>
<style>
.form-group {
margin: 10px 0;
}
.error {
color: red;
font-size: 12px;
}
.success {
color: green;
font-size: 12px;
}
</style>
</head>
<body>
<h1>表單驗證練習</h1>
<form id="myForm">
<div class="form-group">
<label for="username">使用者名稱:</label>
<input type="text" id="username" name="username">
<span id="usernameError" class="error"></span>
</div>

<div class="form-group">
<label for="email">電子郵件:</label>
<input type="email" id="email" name="email">
<span id="emailError" class="error"></span>
</div>

<div class="form-group">
<label for="age">年齡:</label>
<input type="number" id="age" name="age">
<span id="ageError" class="error"></span>
</div>

<button type="submit">提交</button>
</form>

<script>
// 在這裡實作表單驗證
</script>
</body>
</html>

實作要求:

  1. 使用者名稱不能為空,且長度至少 3 個字元
  2. 電子郵件必須符合基本格式
  3. 年齡必須是數字,且範圍在 1-120 之間
  4. 提交時顯示驗證結果
// 實作程式碼
let form = document.getElementById("myForm");
let username = document.getElementById("username");
let email = document.getElementById("email");
let age = document.getElementById("age");

// 驗證函數
function validateUsername() {
let value = username.value.trim();
let error = document.getElementById("usernameError");

if (value === "") {
error.textContent = "使用者名稱不能為空";
return false;
} else if (value.length < 3) {
error.textContent = "使用者名稱至少需要 3 個字元";
return false;
} else {
error.textContent = "";
return true;
}
}

function validateEmail() {
let value = email.value.trim();
let error = document.getElementById("emailError");
let emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;

if (value === "") {
error.textContent = "電子郵件不能為空";
return false;
} else if (!emailRegex.test(value)) {
error.textContent = "請輸入有效的電子郵件格式";
return false;
} else {
error.textContent = "";
return true;
}
}

function validateAge() {
let value = parseInt(age.value);
let error = document.getElementById("ageError");

if (isNaN(value)) {
error.textContent = "請輸入有效的年齡";
return false;
} else if (value < 1 || value > 120) {
error.textContent = "年齡必須在 1-120 之間";
return false;
} else {
error.textContent = "";
return true;
}
}

// 綁定驗證事件
username.addEventListener("blur", validateUsername);
email.addEventListener("blur", validateEmail);
age.addEventListener("blur", validateAge);

// 表單提交事件
form.addEventListener("submit", function(event) {
event.preventDefault(); // 阻止表單預設提交

let isUsernameValid = validateUsername();
let isEmailValid = validateEmail();
let isAgeValid = validateAge();

if (isUsernameValid && isEmailValid && isAgeValid) {
alert("表單驗證成功!");
console.log("表單資料:", {
username: username.value,
email: email.value,
age: age.value
});
} else {
alert("請修正表單錯誤後再提交");
}
});

進階概念

在掌握了基礎的 DOM 操作後,我們可以進一步學習一些進階概念。這些概念將幫助您更深入地理解 DOM 的運作機制,並能夠處理更複雜的網頁互動需求。

DOM 節點操作

創建新元素

// 創建新的 div 元素
let newDiv = document.createElement("div");
newDiv.textContent = "新創建的元素";
newDiv.className = "new-element";

// 添加到頁面
document.body.appendChild(newDiv);

移除元素

// 移除指定元素
let elementToRemove = document.getElementById("elementToRemove");
if (elementToRemove) {
elementToRemove.parentNode.removeChild(elementToRemove);
}

插入元素

// 在指定元素前插入新元素
let newElement = document.createElement("p");
newElement.textContent = "插入的段落";
let targetElement = document.getElementById("target");
targetElement.parentNode.insertBefore(newElement, targetElement);

事件委派

當網頁中有大量相似元素需要相同的事件處理時,為每個元素都綁定事件處理器會造成效能問題。事件委派是一種優雅的解決方案,它利用事件冒泡機制來統一處理多個元素的事件。
當有多個相似元素需要相同事件處理時,可以使用事件委派:

<ul id="todoList">
<li>項目 1</li>
<li>項目 2</li>
<li>項目 3</li>
</ul>
let todoList = document.getElementById("todoList");

// 使用事件委派處理所有 li 元素的點擊
todoList.addEventListener("click", function(event) {
if (event.target.tagName === "LI") {
event.target.style.textDecoration = "line-through";
console.log("點擊了:" + event.target.textContent);
}
});

事件委派的優點:

  • 減少事件監聽器數量
  • 動態添加的元素自動具有事件處理
  • 提高效能

課堂作業

透過實際的專案練習,我們可以將所學知識整合應用。以下作業將幫助您鞏固學習成果,並培養解決實際問題的能力。建議您獨立完成這些作業,這將是檢驗學習效果的最佳方式。

作業一:簡單的待辦事項清單

建立一個待辦事項清單,具備以下功能:

  1. 可以添加新的待辦項目
  2. 點擊項目可以標記為完成(加上刪除線)
  3. 可以刪除已完成的項目
  4. 顯示未完成項目的數量

作業二:顏色選擇器

顏色選擇器是一個實用的工具,結合了多種 DOM 操作技巧。這個作業將考驗您對事件處理、樣式操作和用戶介面設計的綜合運用能力。

建立一個顏色選擇器,具備以下功能:

  1. 使用 RGB 滑桿控制顏色
  2. 即時預覽顏色效果
  3. 可以儲存喜歡的顏色
  4. 顯示當前顏色的十六進位值

跟著做:
完成上述作業後,可以嘗試添加更多功能,如:

  • 鍵盤快捷鍵支援
  • 本地儲存功能
  • 動畫效果

總結

經過這篇教學的學習,您已經掌握了 JavaScript BOM 與 DOM 的核心概念和實作技巧。這些知識是現代網頁開發的基礎,將為您後續學習更進階的 JavaScript 概念奠定堅實的基礎。

在本篇教學中,我們學習了:

  1. BOM 基礎:瀏覽器物件模型的概念和常用方法
  2. DOM 基礎:文件物件模型和元素選擇方法
  3. 內容操作:如何讀取和修改元素內容
  4. 事件處理:響應用戶互動的方法
  5. 實作練習:透過實際範例鞏固學習

這些知識是 JavaScript 網頁開發的基礎,掌握它們將讓您能夠創造豐富的網頁互動體驗。在下一階段,我們將學習更進階的 JavaScript 概念,如非同步程式設計、AJAX 等。