---
# System prepended metadata

title: 瀏覽器 Console 偵錯完全指南

---

# 瀏覽器 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 的執行時機與邏輯。');
```

**記住：永遠不要執行任何您不完全理解的程式碼。祝您偵錯順利！** 🎉