# 瀏覽器 Console 偵錯完全指南
## 📖 前言:告別 F12 亂 Try
你是否也常遇到這種情況:明明寫了 CSS,畫面卻不如預期?或是想用 JavaScript 抓取元素,卻一直回傳 `null`?
多數人會按 F12,在「Elements (元素)」面板上點來點去,靠「肉眼」逐行比對。這就像隔著毛玻璃看問題,耗時且令人脾氣暴躁。
這篇指南將帶您解鎖一個更強大的工具:**Console (主控台)**。
Console 不只是一個顯示錯誤訊息的地方,它是一個**即時、互動的 JavaScript 實驗室**。你可以在這裡直接「存取」並「修改」當前的網頁,像外科手術般精準地找出病灶。
---
## ⚠️ 安全第一:請勿貼上來路不明的程式碼
在開始前,您必須知道「Self-XSS」攻擊。駭客會欺騙您在 Console 貼上一段程式碼,聲稱能開啟隱藏功能,實則竊取您的帳號。
**黃金法則:永遠不要執行任何您不完全理解的程式碼。**
---
## 第一章:核心武器庫
### 1.1 為什麼 Console 比 Elements 面板更強大?
- **Elements (元素) 面板**:讓您「**看**」。您可以看到 HTML 結構和 CSS 規則。
- **Console (主控台)**:讓您「**做**」。您可以在此執行 JavaScript,動態地「**抓取**」、「**修改**」和「**測試**」任何元素。
### 1.2 串連兩個世界的橋樑:`$0`
`$0` 是一個神奇的變數,它會自動指向**您當前在「Elements」面板中選取的那個元素**。
**標準作業流程 (SOP):**
1. 在網頁上對著您想檢查的元素按右鍵,選「檢查」
2. 此時「Elements」面板會自動高亮該元素的 HTML 標籤
3. 切換到旁邊的「Console」面板
4. 輸入 `$0` 並按下 Enter
Console 就會回傳該 DOM 元素物件。**這是我們所有診斷的起點。**
### 1.3 快速選取器:`$` 和 `$$`
在 Console 中快速選取元素的快捷鍵:
#### `$$('CSS 選擇器')`
- **功能:** 等同於 `document.querySelectorAll()`
- **用途:** 抓取頁面上「**所有**」匹配的元素,並以陣列 (NodeList) 回傳
- **範例:**
```javascript
// 抓取頁面上所有 class 為 .product-item 的元素
$$('.product-item')
// 抓取所有圖片
$$('img')
```
#### `$('CSS 選擇器')`
- **功能:** 等同於 `document.querySelector()`
- **用途:** 抓取頁面上「**第一個**」匹配的元素
- **範例:**
```javascript
// 抓取 ID 為 main-nav 的元素
$('#main-nav')
```
### 1.4 進階輸出工具
- `console.log()` / `warn()` / `error()`:印出不同層級的訊息
- **`console.table()`**:將數據以表格呈現,便於「橫向比較」
- **`console.group()`**:將訊息分組,讓報告「可折疊」、更清晰
- **`console.dir()`**:印出可互動的 DOM 物件,而不只是 HTML 字串
---
## 第二章:DOM 結構偵錯實戰
DOM (文件物件模型) 就是 HTML 的結構樹。結構一錯,版面就會全亂。
### 2.1 【入門】我的 JavaScript 為什麼抓不到元素?
**情境:** 您寫了 `document.getElementById('my-btn').click()` 但報錯,您懷疑元素根本沒載入或 ID 寫錯了。
**診斷(使用 `$$`):**
```javascript
// 測試 ID 選擇器
$$('#my-btn').length
// 如果回傳 0,代表 ID 寫錯了,或是元素真的不存在
// 測試 class 選擇器
$$('.important-item').length
// 如果回傳 5,您就知道頁面上有 5 個
```
**解說:** 這能讓您在 1 秒內知道是您的「選擇器語法」有錯,還是「DOM 結構」有錯。
### 2.2 【實戰】我的元素為什麼跑版了?
**情境:** 一個 `<div>` 應該要被包在 ID 為 `#sidebar` 的容器裡,但它跑到外面了。
**診斷(使用 `$0`):**
1. 在 Elements 面板點選那個跑版的 `<div>`
2. 切換到 Console,開始「爬樹」:
```javascript
// 它是誰?
$0
// 它的父元素是誰?
$0.parentElement
// 一看,發現父元素是 <body>,而不是 #sidebar,馬上抓到問題!
// 它的下一個兄弟元素是誰?
$0.nextElementSibling
// 它的上一個兄弟元素是...
$0.previousElementSibling
```
**解說:** `$0.parentElement` 是最常用的指令,90% 的「跑版」問題都能用它來確認父子關係是否如預期。
### 2.3 【進階】快速狀態快照
當您需要快速了解當前頁面的「健康指數」:
```javascript
console.clear();
const snapshotReport = {
"圖示容器數量": document.querySelectorAll('[class*="_Layer-icon_"]').length,
"Layer 項目數量": document.querySelectorAll('[class*="Layer-"]').length,
"已替換圖示": document.querySelectorAll('[data-custom-icon-replaced]').length,
"所有 SVG 元素": document.querySelectorAll('svg').length
};
console.table(snapshotReport);
```
### 2.4 【專家級】複雜 DOM 結構全面分析
**情境:** 您正在處理一個複雜的樹狀結構(例如檔案總管、圖層面板),想要全面了解整個結構的階層關係、樣式配置和互動元素分佈。
**診斷(深度結構掃描):**
這個腳本展示了如何系統化地分析複雜的 DOM 結構,特別適合用於偵錯樹狀元件。
```javascript
console.clear();
console.log('Checking Outline DOM structure...\n');
// 1. 智能尋找容器(容錯機制)
var outline = document.querySelector('[class*="Outline"]');
if (!outline) {
outline = document.querySelector('[class*="LayerTree"]');
}
if (outline) {
console.log('✅ Found Outline container');
console.log('className:', outline.className);
// 2. 統計所有 Layer 元素
var layerRoots = outline.querySelectorAll('[class*="_Layer_"]');
console.log('\nTotal Layer elements found:', layerRoots.length);
// 3. 深度分析前 5 個 Layer
console.log('\n📊 First 5 Layers (詳細分析):');
for (var i = 0; i < Math.min(5, layerRoots.length); i++) {
var layer = layerRoots[i];
var text = layer.textContent.trim().substring(0, 30);
var style = window.getComputedStyle(layer);
var children = layer.querySelectorAll('[class*="_Layer_"]');
console.log('\n' + (i + 1) + '.');
console.log(' Text:', text);
console.log(' Tag:', layer.tagName);
console.log(' Class:', layer.className);
console.log(' padding-left:', style.paddingLeft);
console.log(' Children count:', children.length);
}
// 4. 檢查互動元素(可點擊按鈕)
console.log('\n\n🖱️ Checking clickable buttons:');
var clickables = outline.querySelectorAll('[class*="Layer-clickable"]');
console.log('Total clickable buttons:', clickables.length);
for (var j = 0; j < Math.min(3, clickables.length); j++) {
var btn = clickables[j];
var btnText = btn.textContent.trim().substring(0, 30);
var style2 = window.getComputedStyle(btn);
console.log('\n' + (j + 1) + '.');
console.log(' Text:', btnText);
console.log(' padding-left:', style2.paddingLeft);
}
} else {
console.error('❌ ERROR: Outline container not found');
console.log('💡 建議:檢查選擇器是否正確,或頁面是否已完全載入');
}
```
**解說:**
這個腳本展示了「專業級」DOM 分析的四個關鍵步驟:
1. **容錯機制**:使用 `if (!outline)` 提供備用選擇器,確保在不同情況下都能找到容器
2. **統計總覽**:先取得整體數量,建立全局視角
3. **深度抽樣**:針對前幾個元素進行詳細分析(文字、標籤、class、樣式、子元素數量)
4. **互動元素檢查**:單獨分析可點擊元素,因為它們的樣式配置往往是問題來源
**實戰技巧:**
- **為什麼用 `var` 而不是 `let/const`?** 在 Console 中使用 `var` 可以讓您重複執行腳本而不會出現「變數已宣告」的錯誤
- **為什麼使用 `Math.min(5, array.length)`?** 防止陣列長度不足時出現錯誤
- **如何快速調整抽樣數量?** 修改 `Math.min(5, ...)` 和 `Math.min(3, ...)` 中的數字即可
**何時使用這個腳本?**
- 階層縮排不正確時(檢查 `padding-left` 是否與嵌套深度相關)
- 元素數量異常時(總數與預期不符)
- 互動功能失效時(檢查 clickable 元素是否存在)
- 接手他人程式碼時(快速了解 DOM 結構)
---
## 第三章:CSS 樣式偵錯實戰
這是最常見、也最磨脾氣的環節。您寫的 CSS 規則為什麼無效?
### 3.1 【關鍵觀念】`getComputedStyle` vs `$0.style`
- **`$0.style`**:只能讀取該元素的 *inline style* (寫在 `style="..."` 屬性裡的 CSS)。**99% 的情況下,這不是您要的。**
- **`window.getComputedStyle($0)`**:**這才是神器。** 它可以讀取瀏覽器合併了所有 CSS 檔案、`!important`、inline 樣式後,**「最終計算」出來的樣式**。
### 3.2 【入門】我的顏色為什麼沒套用?
**情境:** 您在 CSS 檔案寫了 `.my-button { color: red; }`,但按鈕依然是藍色。
**診斷(使用 `getComputedStyle`):**
1. 在 Elements 面板點選該按鈕
2. 切換到 Console:
```javascript
// 1. 取得 $0 最終的計算樣式
let styles = window.getComputedStyle($0);
// 2. 檢查 'color' 屬性
console.log(styles.getPropertyValue('color'));
// 結果回傳 "rgb(0, 0, 255)" 或 "blue",您就知道紅色被蓋掉了
// 3. 順便檢查其他樣式
console.log(styles.getPropertyValue('font-size'));
console.log(styles.getPropertyValue('padding-left'));
```
**解說:** 這能讓您立刻知道是哪個規則勝出(高權重),而不是猜測您的 class 名稱是不是寫錯了。
### 3.3 【實戰】Flex 佈局大亂鬥
**情境:** 您想讓一個區塊「水平置中」,所以您在**父容器**寫了 `display: flex;` 和 `justify-content: center;`,結果它卻「垂直置中」了!
**診斷(找出元兇 `flex-direction`):**
1. 在 Elements 面板點選**父容器**(因為 flex 屬性是定義在父層)
2. 切換到 Console:
```javascript
// 1. 取得父容器 ($0) 的最終樣式
let parentStyles = window.getComputedStyle($0);
// 2. 檢查 display 和 justify-content
console.log(parentStyles.getPropertyValue('display'));
// 回傳 'flex'
console.log(parentStyles.getPropertyValue('justify-content'));
// 回傳 'center'
// 3. 檢查關鍵的「軸向」屬性(找出元兇!)
console.log(parentStyles.getPropertyValue('flex-direction'));
// 啊哈!回傳 "column"!
```
**解說:**
一旦 `flex-direction` 變成了 `column`(垂直軸),「主軸」就從水平變成了垂直。
- `justify-content`(主軸對齊)就會變成控制「**垂直**」對齊
- `align-items`(交叉軸對齊)反而變成了控制「**水平**」對齊
您發現了問題:您的 `justify-content: center` 確實生效了,只是它在「垂直軸」上生效。
### 3.4 【進階】即時測試修正 (Live Fix)
**情境:** 承上題,您已經知道問題了。您想**立刻測試**您的修正方案是否有效。
**修正(使用 `$0.style` 強制覆蓋):**
```javascript
// 您的目標是「水平置中」,在 "column" 軸向下,您該用 align-items
$0.style.alignItems = 'center';
// 畫面立刻就水平置中了!
// 您可能還想把垂直對齊改回「靠上」
$0.style.justifyContent = 'flex-start';
```
**解說:** `$0.style.XXX = 'value'` 會在該元素上**強制加上 inline-style**。因為 inline-style 的權重很高,它幾乎能覆蓋掉所有來自 CSS 檔案的規則。
**終極手段(僅供測試):`!important`**
```javascript
// 在 Console 中執行,用 'important' 覆蓋 'important'
$0.style.setProperty('padding-left', '20px', 'important');
```
**警告:** 這只該用於「測試」,請不要在您的正式程式碼中濫用 `!important`。
### 3.5 【專家級】CSS 來源偵測
**情境:** 找出到底是哪個樣式在作祟。
```javascript
console.clear();
const firstLayer = document.querySelector('[class*="Layer-clickable"]');
if (firstLayer) {
// 檢查目標元素
console.group('🎯 目標元素分析');
const allStyles = window.getComputedStyle(firstLayer);
console.log(' inline style:', firstLayer.style.paddingLeft || '(無)');
console.log(' computed style:', allStyles.paddingLeft);
console.log(' 是否為 !important:',
allStyles.getPropertyPriority('padding-left') === 'important' ? '🔥 是' : '❌ 否');
console.dir(firstLayer);
console.groupEnd();
// 檢查父元素鏈
console.group('🌳 父元素鏈分析');
let parent = firstLayer.parentElement;
let level = 0;
let parentData = [];
while (parent && level < 5) {
const parentStyles = window.getComputedStyle(parent);
parentData.push({
'層級': `(父 ${level + 1})`,
'Class': (parent.className || '').substring(0, 50),
'display': parentStyles.display,
'flex-direction': parentStyles.flexDirection || 'N/A'
});
parent = parent.parentElement;
level++;
}
console.table(parentData);
console.groupEnd();
}
```
---
## 第四章:實用範例集錦
### 4.1 找出頁面上所有「壞掉的圖片」
```javascript
// 1. 抓取所有 <img>
let allImages = $$('img');
// 2. 篩選出有問題的(已載入完成,但自然寬度為 0)
let brokenImages = Array.from(allImages).filter(img => {
return img.complete && img.naturalWidth === 0;
});
// 3. 用表格印出所有壞圖的 src
console.table(brokenImages.map(img => ({ src: img.src })));
```
### 4.2 檢查網頁親和性 (Accessibility)
找出所有沒有 `alt`(替代文字)的圖片:
```javascript
// 使用 CSS 選擇器 :not([alt])
let imagesWithoutAlt = $$('img:not([alt])');
console.log(`警告:發現 ${imagesWithoutAlt.length} 張圖片沒有 alt 屬性!`);
console.log(imagesWithoutAlt);
```
### 4.3 模擬 JavaScript 事件
```javascript
// 抓取按鈕並模擬點擊
$('#my-btn').click();
// 模擬在輸入框中輸入
let input = $('#my-input');
input.value = '測試文字';
// 注意:這不會觸發 React 等框架的 state 改變
```
### 4.4 DOM 差異對比 (Before vs. After)
```javascript
console.clear();
console.log('🔍 對比 DOM 結構...\n');
const replacedIcon = document.querySelector('[data-custom-icon-replaced]');
const originalIcon = document.querySelector('[class*="_Layer-icon_"]:not([data-custom-icon-replaced])');
// 已替換的
console.group('✅ 已替換的圖示容器');
if (replacedIcon) {
console.dir(replacedIcon);
console.log('子元素標籤:', Array.from(replacedIcon.children).map(c => c.tagName));
}
console.groupEnd();
// 原始的
console.group('❌ 原始的圖示容器');
if (originalIcon) {
console.dir(originalIcon);
console.log('子元素標籤:', Array.from(originalIcon.children).map(c => c.tagName));
}
console.groupEnd();
```
---
## 第五章:動態與非同步偵錯
處理前端最棘手的問題:**時序 (Timing)**。
### 5.1 非同步驗證(等待 React 渲染)
您的腳本可能在 React/Vue 渲染完成*之前*就執行了:
```javascript
console.log('⏰ 腳本已啟動... 將在 3 秒後執行最終檢查...');
setTimeout(() => {
console.clear();
console.group('⏰ 3 秒後的最終檢查');
// 檢查總覽
const replaced = document.querySelectorAll('[data-custom-icon-replaced]').length;
const total = document.querySelectorAll('[class*="_Layer-icon_"]').length;
console.log('✅ 已替換圖示:', replaced, '/', total);
// 抽樣檢查第一個
const firstIcon = document.querySelector('[class*="_Layer-icon_"]');
if (firstIcon) {
console.group('📦 第一個圖示容器檢查:');
console.log('有 data-custom-icon-replaced 屬性?',
firstIcon.hasAttribute('data-custom-icon-replaced'));
console.dir(firstIcon);
console.groupEnd();
}
console.groupEnd();
}, 3000);
```
### 5.2 動態穩定性監控(偵測無限迴圈)
DOM 元素是否在*持續增加*?
```javascript
console.clear();
console.log('🔄 開始 10 秒穩定性監控...');
let count = 0;
let countHistory = [];
const checkInterval = setInterval(() => {
count++;
const replaced = document.querySelectorAll('[data-custom-icon-replaced]').length;
const total = document.querySelectorAll('[class*="_Layer-icon_"]').length;
countHistory.push(total);
console.log(`${count}秒: 已替換 ${replaced} / 總數 ${total}`);
if (count >= 10) {
clearInterval(checkInterval);
// 最終判斷
console.log('\n' + '='.repeat(50));
console.log('🎯 監控完成!最終診斷報告:');
const finalTotal = countHistory[countHistory.length - 1];
const isStable = finalTotal === countHistory[countHistory.length - 2];
console.log(`10秒計數歷史: [${countHistory.join(', ')}]`);
if (finalTotal > 110) {
console.error('❌ 失敗!數量超限,可能存在無限迴圈。');
} else if (!isStable) {
console.warn('⚠️ 注意!數量未超限,但在最後一秒仍有波動。');
} else {
console.log('✅ 數量正常且穩定。');
}
console.log('='.repeat(50));
}
}, 1000);
```
---
## 總結:您的偵錯 SOP
Console 偵錯的核心流程:
1. **鎖定 (Select):** 用 Elements 面板 + `$0` 鎖定元素
2. **診斷 (Inspect):** 用 `getComputedStyle` 檢查 CSS,用 `.parentElement` 檢查 DOM
3. **測試 (Test):** 用 `$0.style` 即時修改樣式,直到找出解法
4. **修復 (Fix):** 回到您的 VS Code 或編輯器中,永久修復它
下次再遇到磨脾氣的前端問題時,試試這套流程吧!
---
## 附錄:進階偵錯範例
### 深度診斷:為什麼功能沒有執行?
```javascript
console.clear();
console.log('🔍 開始詳細診斷...\n');
// 1. 收集現狀
const replacedAny = document.querySelectorAll('[data-custom-icon-replaced]');
console.log('🎯 替換狀態:');
console.log('已找到替換標記:', replacedAny.length, '個');
// 2. 抽樣檢查(前 5 個)
console.groupCollapsed('📝 前 5 個組件的詳細資訊');
const layers = document.querySelectorAll('[class*="Layer-"]');
Array.from(layers).slice(0, 5).forEach((layer, index) => {
const iconDiv = layer.querySelector('[class*="_Layer-icon_"]');
const textContent = (layer.textContent || '').trim();
const hasReplaced = iconDiv?.hasAttribute('data-custom-icon-replaced');
console.group(`${index + 1}. ${textContent.substring(0, 20)}`);
console.log('已替換標記:', hasReplaced ? '✅ 是' : '❌ 否');
console.log('圖示容器 DOM:');
console.dir(iconDiv);
console.groupEnd();
});
console.groupEnd();
// 3. 提出假說
if (replacedAny.length === 0) {
console.warn('\n⚠️ 沒有任何圖示被替換!可能原因:');
console.log('1. replaceOutlineIcons 函數沒有執行');
console.log('2. 組件名稱匹配失敗');
console.log('3. 代碼沒有正確部署(需重啟 dev 伺服器)');
}
// 4. 實驗驗證
console.group('\n🧪 測試手動替換第一個圖示');
const firstIcon = document.querySelector('[class*="_Layer-icon_"]');
if (firstIcon) {
firstIcon.setAttribute('data-test-manual', 'true');
const hasTestAttr = firstIcon.hasAttribute('data-test-manual');
console.log('手動屬性設置成功:', hasTestAttr ? '✅ 是' : '❌ 否');
if (hasTestAttr) {
console.log('✅ DOM 操作正常,問題出在 replaceOutlineIcons 邏輯');
}
}
console.groupEnd();
console.log('\n💡 建議: 請檢查 replaceOutlineIcons 的執行時機與邏輯。');
```
**記住:永遠不要執行任何您不完全理解的程式碼。祝您偵錯順利!** 🎉