# 瀏覽器 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 的執行時機與邏輯。'); ``` **記住:永遠不要執行任何您不完全理解的程式碼。祝您偵錯順利!** 🎉