🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

說實話,在我從事 JavaScript 開發的頭三年裡,我一直以為自己已經掌握了 console API。我的意思是,這能有多難?不就是console.log()嗎?出錯的時候可以用console.error() ,如果想更高級一點,還console.warn() 。就這些,這就是全部的工具包了。

有一天,我和一位資深開發人員配對程式設計時,看到她用console.time()console.table()花了大約十五分鐘除錯了一個棘手的效能問題。我坐在那裡,震驚不已,意識到自己之前一直像個原始人一樣除錯,而瀏覽器裡明明有一大堆工具卻一直閒置著。

關鍵在於:控制台 API 非常龐大。大多數開發者可能只用到了其中 10% 的功能。剩下的 90% 呢?它們就靜靜地待在那裡,等著幫你節省數小時的痛苦、挫敗感,以及那些深夜除錯時,你像絕望地留下麵包屑一樣,不停地加入console.log('here1')console.log('here2')console.log('here3')的場景。

過去十年,我從事過各種各樣的專案,從處理數百萬請求的電子商務平台到複雜的資料視覺化工具,我可以非常肯定地告訴你:掌握這些鮮為人知的控制台方法將從根本上改變你的除錯方式。你會更快找到 bug,更能理解效能瓶頸,而且說實話,你會感覺自己解鎖了一種超能力。

那麼,讓我們深入了解十個大多數開發者不知道存在,但絕對應該了解的控制台方法。


  1. console.table() - 因為你的陣列和物件值得更好的處理方式

它的功能

console.table() ` 函數接受陣列和物件作為參數,並將它們渲染成控制台中清晰易讀的表格。無需再費力辨認嵌套物件表示法或費心理解陣列索引。只需查看簡潔、結構化的資料,就像在電子表格中一樣。

為什麼開發者會忽略它

我們大多數人從第一天起就學會了使用console.log() ,而且一直沒換過。我們都是習慣的奴隸。再說, console.log()確實好用,那為什麼要去改呢?問題在於, console.log()會讓複雜的資料結構看起來像一鍋亂燉。你只能不停地展開小箭頭,滾動瀏覽嵌套的物件,最後甚至搞不清楚自己到底在看什麼。

如何使用它

語法非常簡單:

console.table(data, [columns]);

第一個參數是你的資料(陣列或物件)。第二個參數是可選的,用於指定要顯示的列。

實際程式碼範例

範例 1:基本物件陣列

const users = [
  { id: 1, name: 'Sarah Chen', role: 'Developer', active: true },
  { id: 2, name: 'Marcus Thompson', role: 'Designer', active: true },
  { id: 3, name: 'Elena Rodriguez', role: 'Product Manager', active: false },
  { id: 4, name: 'James Wilson', role: 'Developer', active: true }
];

console.table(users);

這將產生一個美觀的表格,包含索引、ID、名稱、角色和活躍狀態等欄位。每一行都完美對齊。您可以立即發現規律,找到不活躍用戶,並一目了然地了解資料結構。

範例 2:篩選列

// Only show name and role columns
console.table(users, ['name', 'role']);

當你處理擁有幾十個屬性但你只關心其中幾個屬性的物件時,這非常有用。

例 3:物件之物件

const apiResponses = {
  github: { status: 200, time: 145, cached: false },
  twitter: { status: 200, time: 312, cached: true },
  stripe: { status: 503, time: 5000, cached: false },
  sendgrid: { status: 200, time: 89, cached: true }
};

console.table(apiResponses);

現在你可以立即看出 Stripe 宕機了,響應速度極慢,而 SendGrid 速度飛快,而且有快取。如果你嘗試從console.log(apiResponses)獲取這些訊息,你可能要花上一整天的時間。

範例 4:嵌套資料(需注意)

const complexData = [
  { 
    user: 'Alice', 
    stats: { posts: 45, likes: 230 },
    lastLogin: new Date('2024-11-28')
  },
  { 
    user: 'Bob', 
    stats: { posts: 12, likes: 89 },
    lastLogin: new Date('2024-11-30')
  }
];

console.table(complexData);

你會注意到嵌套物件顯示為[object Object] 。這是一個限制。對於巢狀資料,你可能需要先將其扁平化,或專門對嵌套部分使用console.table()

專業提示

  1. 結合陣列方法:在鍊式呼叫的末尾使用console.table()來查看轉換結果:
   console.table(
     users
       .filter(u => u.active)
       .map(u => ({ name: u.name, role: u.role }))
   );
  1. 顯示前排序:瀏覽器不會自動對表格進行排序,因此請先準備好您的資料:
   console.table(users.sort((a, b) => a.name.localeCompare(b.name)));
  1. 用於 API 回應偵錯:當您使用傳回資料陣列的 REST API 時, console.table()是您的最佳幫手。您可以立即發現不一致之處、缺失欄位或意外值。

進階用例

性能比較表

const performanceMetrics = [];

function measureOperation(name, fn) {
  const start = performance.now();
  fn();
  const end = performance.now();
  performanceMetrics.push({
    operation: name,
    duration: `${(end - start).toFixed(2)}ms`
  });
}

measureOperation('For Loop', () => {
  for (let i = 0; i < 100000; i++) { /* work */ }
});

measureOperation('forEach', () => {
  Array.from({ length: 100000 }).forEach(() => { /* work */ });
});

measureOperation('Map', () => {
  Array.from({ length: 100000 }).map(() => { /* work */ });
});

console.table(performanceMetrics);

現在,你的遊戲主機上就能清楚顯示效能比較資訊了。

資料庫查詢結果

如果你使用的是 MongoDB 之類的資料庫或處理 SQL 查詢結果(轉換為 JSON 格式),那麼console.table()可以讓查看查詢結果變得比查看原始日誌容易得多。

應避免的錯誤

  1. 不要用於處理海量陣列:在表格中顯示 10,000 行資料會導致瀏覽器卡頓。請先篩選資料。

  2. 注意循環引用:如果你的物件存在循環引用, console.table()可能無法正確處理。 Chrome 通常可以處理,但 Firefox 可能會出錯。

  3. 請記住,它是唯讀的:您無法編輯控制台表格中的值並將其反映到您的程式碼中。它僅用於可視化。

它如何節省除錯時間

想像一下,你要除錯一個處理使用者資料的函數。使用console.log() ,你會看到類似這樣的輸出:

[{id: 1, name: "Sarah", ...}, {id: 2, name: "Marcus", ...}, ...]

你需要點擊才能展開、滾動,還要在腦海中比較數值。而使用console.table() ,所有內容一目了然。你可以立即發現使用者 ID 7 的郵件地址為空,或有三個使用者的電話號碼格式錯誤。原本需要點擊和滾動五分鐘才能完成的工作,現在只需五秒鐘就能搞定。


  1. console.time() 和 console.timeEnd()-你的性能除錯瑞士軍刀

他們做什麼

這兩個方法配合使用。 console.timeconsole.time()會啟動一個帶有特定標籤的計時器,而console.timeEnd()`會停止計時器並記錄經過的時間。這就像在你的程式碼中直接內建了一個秒錶。

為什麼開發者會忽略它們

大多數開發者都知道 DevTools 中的「效能」選項卡,它非常適合進行複雜的效能分析。但有時你並不需要火焰圖——你只需要知道某個函數是否執行緩慢。使用Date.now()performance.now()並手動計算時間感覺很笨拙。 console.timeconsole.time()`如此簡單,以至於很多人甚至沒有意識到它的存在。

如何使用它們

console.time('labelName');
// ... code you want to measure ...
console.timeEnd('labelName');

標籤必須完全匹配。瀏覽器會記錄類似這樣的資訊: labelName: 234.56ms

實際程式碼範例

範例 1:基本功能時序

console.time('fetchUserData');

async function fetchUserData() {
  const response = await fetch('/api/users');
  const data = await response.json();
  return data;
}

const users = await fetchUserData();
console.timeEnd('fetchUserData');
// Output: fetchUserData: 347.82ms

範例 2:演算法效能比較

const largeArray = Array.from({ length: 100000 }, (_, i) => i);

// Test forEach
console.time('forEach');
largeArray.forEach(num => num * 2);
console.timeEnd('forEach');

// Test map
console.time('map');
largeArray.map(num => num * 2);
console.timeEnd('map');

// Test for loop
console.time('for-loop');
for (let i = 0; i < largeArray.length; i++) {
  largeArray[i] * 2;
}
console.timeEnd('for-loop');

// Outputs might be:
// forEach: 8.23ms
// map: 12.45ms
// for-loop: 3.67ms

現在你已經獲得了經驗資料,可以判斷在你的具體情況下哪種方法最快。

範例 3:巢狀定時器

console.time('entire-operation');

console.time('step-1-database');
await database.query('SELECT * FROM users');
console.timeEnd('step-1-database');

console.time('step-2-processing');
const processed = processData(rawData);
console.timeEnd('step-2-processing');

console.time('step-3-rendering');
renderToDOM(processed);
console.timeEnd('step-3-rendering');

console.timeEnd('entire-operation');

// Outputs:
// step-1-database: 234.12ms
// step-2-processing: 45.67ms
// step-3-rendering: 12.34ms
// entire-operation: 292.45ms

這可以準確地告訴你瓶頸在哪裡。在這個例子中,資料庫查詢佔用了你 80% 的時間。

範例 4:使用者互動時機

button.addEventListener('click', () => {
  console.time('button-click-handler');

  // Simulate complex operations
  const result = performHeavyCalculation();
  updateUI(result);

  console.timeEnd('button-click-handler');
});

如果日誌顯示button-click-handler: 1247.89ms ,你就知道為什麼使用者抱怨 UI 運作緩慢了。

專業提示

  1. 使用描述性標籤:不要使用像“timer1”或“test”這樣的通用標籤。請使用'api-fetch-user-profile''sort-10k-products' 。未來的你會感謝我的。

  2. 配合 console.timeLog() 使用:也可以使用console.timeLog(label)記錄目前經過的時間,而不會停止計時器:

   console.time('long-operation');

   await step1();
   console.timeLog('long-operation'); // long-operation: 123ms

   await step2();
   console.timeLog('long-operation'); // long-operation: 456ms

   await step3();
   console.timeEnd('long-operation'); // long-operation: 789ms
  1. 使用包裝器自動計時:建立一個實用函數:
   async function timeAsync(label, asyncFn) {
     console.time(label);
     try {
       return await asyncFn();
     } finally {
       console.timeEnd(label);
     }
   }

   const data = await timeAsync('fetch-data', () => fetch('/api/data'));

進階用例

A/B 測試性能

function measureImplementation(name, implementation, iterations = 1000) {
  console.time(name);
  for (let i = 0; i < iterations; i++) {
    implementation();
  }
  console.timeEnd(name);
}

measureImplementation('string-concat', () => {
  let str = '';
  for (let i = 0; i < 1000; i++) str += 'x';
});

measureImplementation('array-join', () => {
  const arr = [];
  for (let i = 0; i < 1000; i++) arr.push('x');
  arr.join('');
});

延遲載入效能

console.time('initial-bundle');
// Initial JavaScript execution
console.timeEnd('initial-bundle');

button.addEventListener('click', async () => {
  console.time('dynamic-import');
  const module = await import('./heavy-feature.js');
  console.timeEnd('dynamic-import');

  console.time('feature-initialization');
  module.initialize();
  console.timeEnd('feature-initialization');
});

應避免的錯誤

  1. 忘記呼叫 timeEnd() :如果忘記呼叫,計時器會一直保持開啟。不會報錯,也不會發出警告。務必確保它們始終配對使用。

  2. 標籤不符console.time('myTimer')console.timeEnd('mytimer')無法正常運作。 JavaScript 區分大小寫。

  3. 請勿用於生產環境監控:這些是開發工具。對於生產環境效能監控,請使用專業的 APM 工具,例如 New Relic、Datadog 或具有分析功能的效能 API。

  4. 避免在不使用 await 的情況下執行非同步程式碼:這樣做行不通:

   console.time('fetch');
   fetch('/api/data'); // Don't await
   console.timeEnd('fetch'); // Logs ~0ms because fetch hasn't completed

它如何節省除錯時間

你收到報告說你的應用程式執行緩慢。該從何入手呢?與其猜測或進行複雜的效能分析,不如直接在可疑的程式碼區塊周圍加上console.time()console.timeEnd() 。幾分鐘之內,你就發現問題所在:沒錯,拖慢速度的是影像處理函數,每張影像的處理時間長達 2 秒。現在,你清楚地知道該從哪裡進行優化了。

如果沒有這些方法,你就得使用performance.now() ,手動進行減法運算,並在程式碼中堆砌計時邏輯。 console.timeconsole.time()`更簡潔、更易讀,也更容易實現。


  1. console.trace() - 順著線索追溯到源頭

它的功能

console.trace()會將堆疊追蹤資訊列印到控制台,顯示導致執行到該行程式碼的完整呼叫路徑。它就像程式碼的“我是怎麼走到這一步的?”按鈕。

為什麼開發者會忽略它

大多數開發者只有在拋出錯誤時才會看到堆疊追蹤訊息,而他們沒有意識到可以按需產生這些資訊。在偵錯具有深層呼叫堆疊的複雜應用程式時,了解函數是如何被呼叫的,通常比知道函數是否被呼叫更重要。

如何使用它

console.trace('Optional label');

就這樣。瀏覽器會從這一點開始輸出完整的堆疊追蹤資訊。

實際程式碼範例

範例 1:追蹤函數呼叫

function calculateTotal(items) {
  console.trace('calculateTotal called');
  return items.reduce((sum, item) => sum + item.price, 0);
}

function processOrder(order) {
  const total = calculateTotal(order.items);
  return total;
}

function handleCheckout(userId) {
  const order = getOrderForUser(userId);
  processOrder(order);
}

handleCheckout(12345);

// Output shows:
// console.trace: calculateTotal called
//   calculateTotal @ app.js:2
//   processOrder @ app.js:7
//   handleCheckout @ app.js:12
//   (anonymous) @ app.js:15

現在您可以看到確切的路徑: handleCheckoutprocessOrdercalculateTotal

範例 2:除錯事件處理程序

function handleClick(event) {
  console.trace('Click handler triggered');
  updateUI();
}

document.addEventListener('click', handleClick);

// When clicked, you'll see exactly what triggered the click:
// Maybe it was user interaction, or maybe another script called .click()

範例 3:追蹤 React 重新渲染

function UserProfile({ userId }) {
  console.trace('UserProfile rendering');

  const user = useUser(userId);
  return <div>{user.name}</div>;
}

如果此元件意外重新渲染,堆疊追蹤將顯示哪個父元件更新觸發了它。

範例 4:除錯遞歸

function factorial(n) {
  if (n === 1) {
    console.trace('Base case reached');
    return 1;
  }
  return n * factorial(n - 1);
}

factorial(5);

// Shows the complete recursive call stack

專業提示

  1. 新增標籤以提供上下文:始終包含描述性訊息:
   console.trace(`User ${userId} reached checkout`);
  1. 結合條件語句:僅在發生意外狀況時進行追蹤:
   function updateCart(item) {
     if (!item.price) {
       console.trace('Item has no price - this should never happen');
     }
     // rest of code
   }
  1. 在庫/框架程式碼中使用:使用第三方程式庫時,追蹤呼叫以了解它們的呼叫方式:
   const originalFetch = window.fetch;
   window.fetch = function(...args) {
     console.trace('Fetch called');
     return originalFetch.apply(this, args);
   };

進階用例

追蹤狀態突變

const state = {
  _count: 0,
  get count() {
    return this._count;
  },
  set count(value) {
    console.trace(`Count changed from ${this._count} to ${value}`);
    this._count = value;
  }
};

state.count = 5; // Shows who changed it

尋找記憶體洩漏源

class CacheManager {
  constructor() {
    this.cache = new Map();
  }

  set(key, value) {
    if (this.cache.size > 1000) {
      console.trace('Cache exceeded 1000 entries - potential memory leak');
    }
    this.cache.set(key, value);
  }
}

除錯第三方集成

// Wrap a third-party function to see when it's called
const analytics = window.analytics;
window.analytics.track = function(...args) {
  console.trace('Analytics track called with:', args);
  return analytics.track.apply(this, args);
};

應避免的錯誤

  1. 不要將它們留在生產環境中:堆疊追蹤會降低效能。發布前請將其移除。

  2. 注意瀏覽器差異:Chrome、Firefox 和 Safari 的堆疊追蹤格式各不相同。不要依賴程式自動解析輸出。

  3. 非同步堆疊追蹤可能具有誤導性:使用 async/await 和 Promise 時,堆疊追蹤可能無法顯示全部情況。現代瀏覽器雖然支援非同步堆疊追蹤,但並非總是完美無缺。

  4. 過多的追蹤資訊等於雜訊:如果追蹤所有內容,輸出結果將淹沒在海量資訊中。要精準控制。

它如何節省除錯時間

你遇到了一個 bug,某個值被錯誤地設定了,但你不知道錯在哪裡。你可以到處加入console.log()語句,也可以只加入一條console.trace()語句,在錯誤值被設定的地方記錄一次,就能立即看到呼叫路徑。

我曾經除錯過一個 Redux action,它被從一個意想不到的地方分發出去。與其在成千上萬行程式碼中苦苦搜尋,不如直接在 reducer 中加入console.trace() 。堆疊追蹤直接指向了一個我不知道的第三方函式庫,它正在分發我之前不知道的 action。這幫我節省了幾個小時。


  1. console.assert() - 內聯完整性檢查

它的功能

console.assert()會判斷一個條件,只有當條件為假時才會記錄錯誤。它就像一個內建在執行時程式碼中的小型單元測試。

為什麼開發者會忽略它

大多數開發者認為斷言是 Jest 或 Mocha 等測試框架的功能,卻忽略了控制台 API 本身就內建了斷言功能。而且,它不像console.log()那樣顯眼,所以在除錯時往往被忽略。

如何使用它

console.assert(condition, message, ...optionalData);

如果condition為真,則什麼事也不發生。如果條件為假,則會在控制台中顯示錯誤訊息。

實際程式碼範例

範例 1:基本斷言

function divide(a, b) {
  console.assert(b !== 0, 'Divide by zero error', { a, b });
  return a / b;
}

divide(10, 2); // No output
divide(10, 0); // Assertion failed: Divide by zero error {a: 10, b: 0}

範例 2:類型檢查

function processUser(user) {
  console.assert(typeof user === 'object', 'User must be an object');
  console.assert('id' in user, 'User must have an id', user);
  console.assert('email' in user, 'User must have an email', user);

  // Process user...
}

processUser({ id: 1 }); // Assertion failed: User must have an email

範例 3:陣列驗證

function processItems(items) {
  console.assert(Array.isArray(items), 'Items must be an array', items);
  console.assert(items.length > 0, 'Items array cannot be empty');
  console.assert(
    items.every(item => item.id),
    'All items must have an id',
    items.filter(item => !item.id)
  );

  // Process items...
}

例 4:狀態不變數

class ShoppingCart {
  constructor() {
    this.items = [];
    this.total = 0;
  }

  addItem(item) {
    this.items.push(item);
    this.total += item.price;

    // Assert invariant: total should equal sum of item prices
    const expectedTotal = this.items.reduce((sum, i) => sum + i.price, 0);
    console.assert(
      this.total === expectedTotal,
      'Cart total mismatch',
      { actual: this.total, expected: expectedTotal }
    );
  }
}

專業提示

  1. 包含上下文資料:可選參數可以是任何值:
   console.assert(
     user.age >= 18,
     'User must be 18+',
     { user, currentAge: user.age, required: 18 }
   );
  1. 用於前提條件:檢查函數開始時的假設:
   function updateProfile(userId, updates) {
     console.assert(userId, 'userId is required');
     console.assert(Object.keys(updates).length > 0, 'updates cannot be empty');
     // Function logic...
   }
  1. 檢查 API 回應:驗證來自外部來源的資料:
   async function fetchUser(id) {
     const response = await fetch(`/api/users/${id}`);
     const user = await response.json();

     console.assert(user.id === id, 'Response ID mismatch', { requested: id, received: user.id });
     console.assert(user.email, 'User missing email', user);

     return user;
   }

進階用例

績效聲明

function criticalOperation() {
  const start = performance.now();

  // ... operation ...

  const duration = performance.now() - start;
  console.assert(
    duration < 100,
    'Operation took too long',
    { duration: `${duration}ms`, threshold: '100ms' }
  );
}

僅開發檢查

const isDev = process.env.NODE_ENV === 'development';

function transferFunds(from, to, amount) {
  if (isDev) {
    console.assert(from.balance >= amount, 'Insufficient funds');
    console.assert(amount > 0, 'Amount must be positive');
    console.assert(from.id !== to.id, 'Cannot transfer to same account');
  }

  // Transfer logic...
}

React 元件屬性驗證

function UserCard({ user, onEdit, theme }) {
  console.assert(user, 'UserCard: user prop is required');
  console.assert(user.name, 'UserCard: user.name is required', user);
  console.assert(
    typeof onEdit === 'function',
    'UserCard: onEdit must be a function',
    { onEdit }
  );
  console.assert(
    ['light', 'dark'].includes(theme),
    'UserCard: invalid theme',
    { theme }
  );

  // Component render...
}

應避免的錯誤

  1. 不要用於錯誤處理console.assert()不會拋出錯誤或停止執行,它只會記錄日誌。若要進行真正的錯誤處理,請使用throw
   // Wrong - code continues executing
   console.assert(user.isAdmin, 'Must be admin');
   deleteAllUsers(); // Still runs!

   // Right - stops execution
   if (!user.isAdmin) throw new Error('Must be admin');
   deleteAllUsers(); // Doesn't run
  1. 斷言不應產生副作用:不要這樣做:
   console.assert(userId = getUserId(), 'No user ID'); // Assignment in assertion!
  1. 注意真值/假值:請記住0''null都是假值:
   console.assert(count, 'Count is required'); // Fails when count is 0!
   console.assert(count !== undefined, 'Count is required'); // Better

它如何節省除錯時間

斷言充當執行時文件和預警系統。它不會讓神秘的錯誤在程式碼深處(例如三層函數之後)才顯現出來,而是在入口點捕獲錯誤資料,並準確地告訴你哪裡出了問題。

我在開發過程中大量使用斷言。它們幫我發現了很多 bug,像是參數傳遞順序錯誤、忘記處理邊界情況,或是對資料結構做出錯誤的假設。斷言會立即在問題根源處失敗,而不是在之後拋出晦澀難懂的錯誤訊息。


  1. console.group() 和 console.groupEnd() - 像專業人士一樣組織你的控制台輸出

他們做什麼

這些方法允許你在控制台輸出中建立可折疊的分組。 console.groupconsole.group()console.groupEnd()`之間記錄的所有內容都會縮進,並且可以折疊/展開。你可以把它想像成檔案系統中的資料夾,只不過是用於日誌的。

為什麼開發者會忽略它們

當你只是記錄一些簡單的資訊時,組織結構並不重要。但當你記錄包含多個步驟、巢狀函數呼叫或涉及大量元件的複雜操作時,控制台就會變得一團糟,難以閱讀。大多數開發人員始終沒有意識到如何組織控制台輸出。

如何使用它們

console.group('Label');
// ... logs ...
console.groupEnd();

// Or use console.groupCollapsed() to start collapsed
console.groupCollapsed('Label');
// ... logs ...
console.groupEnd();

實際程式碼範例

範例 1:基本分組

console.group('User Login Process');
console.log('Validating credentials...');
console.log('Checking database...');
console.log('Generating session token...');
console.log('Setting cookies...');
console.groupEnd();

console.group('Loading User Data');
console.log('Fetching profile...');
console.log('Fetching preferences...');
console.log('Fetching notifications...');
console.groupEnd();

現在,原本是 8 個日誌的扁平列表,變成了兩個組織有序的群組,清楚地顯示了不同的操作。

範例 2:嵌套組

console.group('Processing Order #12345');

  console.group('Validating Items');
    console.log('Item 1: Widget - $10.99 ✓');
    console.log('Item 2: Gadget - $24.99 ✓');
    console.log('Total items: 2');
  console.groupEnd();

  console.group('Payment Processing');
    console.log('Payment method: Credit Card');
    console.log('Amount: $35.98');
    console.log('Status: Approved');
  console.groupEnd();

  console.group('Inventory Update');
    console.log('Reducing Widget stock: 150 → 149');
    console.log('Reducing Gadget stock: 75 → 74');
  console.groupEnd();

console.groupEnd();

這樣就形成了一個非常容易閱讀和理解的層級結構。

範例 3:除錯 API 呼叫

async function fetchUserData(userId) {
  console.group(`API Call: GET /users/${userId}`);

  console.log('Request initiated at:', new Date().toISOString());

  try {
    const response = await fetch(`/api/users/${userId}`);

    console.group('Response Details');
      console.log('Status:', response.status);
      console.log('Headers:', response.headers);
    console.groupEnd();

    const data = await response.json();

    console.group('Response Data');
      console.table(data);
    console.groupEnd();

    console.log('✓ Request completed successfully');

  } catch (error) {
    console.error('✗ Request failed:', error);
  }

  console.groupEnd();
}

範例 4:React 元件生命週期

class UserProfile extends React.Component {
  componentDidMount() {
    console.group(`${this.constructor.name} Lifecycle`);
    console.log('componentDidMount');
    this.loadUserData();
    console.groupEnd();
  }

  componentDidUpdate(prevProps) {
    if (prevProps.userId !== this.props.userId) {
      console.group(`${this.constructor.name} Update`);
      console.log('User changed:', prevProps.userId, '→', this.props.userId);
      this.loadUserData();
      console.groupEnd();
    }
  }

  loadUserData() {
    console.group('Loading User Data');
    console.log('User ID:', this.props.userId);
    // ... fetch logic ...
    console.groupEnd();
  }
}

專業提示

  1. 對非關鍵資訊使用分組折疊:預設將分組折疊起來,以保持控制台簡潔:
   console.groupCollapsed('Detailed Debug Info');
   console.log('Memory usage:', performance.memory.usedJSHeapSize);
   console.log('Network status:', navigator.onLine);
   console.groupEnd();
  1. 始終將 group() 和 groupEnd() 配對:使用 try/finally 來確保清理工作:
   console.group('Critical Operation');
   try {
     riskyOperation();
   } finally {
     console.groupEnd(); // Always closes even if error is thrown
   }
  1. 新增視覺指示器:使用表情符號或符號使群組易於瀏覽:
   console.group('⚠️ Validation Errors');
   console.group('✓ Successful Operations');
   console.group('🔍 Debug Info');

進階用例

自動功能追蹤

function trace(fn, name) {
  return function(...args) {
    console.group(`Function: ${name || fn.name}`);
    console.log('Arguments:', args);

    try {
      const result = fn.apply(this, args);
      console.log('Return value:', result);
      return result;
    } catch (error) {
      console.error('Error:', error);
      throw error;
    } finally {
      console.groupEnd();
    }
  };
}

const tracedAdd = trace((a, b) => a + b, 'add');
tracedAdd(5, 3);

// Output:
// Function: add
//   Arguments: [5, 3]
//   Return value: 8

Redux 操作日誌

const actionLogger = store => next => action => {
  console.group(`Action: ${action.type}`);
  console.log('Payload:', action.payload);
  console.log('Previous State:', store.getState());

  const result = next(action);

  console.log('New State:', store.getState());
  console.groupEnd();

  return result;
};

測試套件組織

function describe(suiteName, tests) {
  console.group(`Test Suite: ${suiteName}`);
  tests();
  console.groupEnd();
}

function it(testName, testFn) {
  try {
    testFn();
    console.log(`✓ ${testName}`);
  } catch (error) {
    console.error(`✗ ${testName}`, error);
  }
}

describe('Calculator', () => {
  it('should add numbers', () => {
    console.assert(add(2, 3) === 5);
  });

  it('should multiply numbers', () => {
    console.assert(multiply(2, 3) === 6);
  });
});

團隊績效分析

class PerformanceMonitor {
  static start(label) {
    console.group(`⏱️ ${label}`);
    console.time(label);
  }

  static end(label) {
    console.timeEnd(label);
    console.groupEnd();
  }

  static checkpoint(message) {
    console.log(`  ↳ ${message}`);
  }
}

PerformanceMonitor.start('Page Load');
PerformanceMonitor.checkpoint('DOM Ready');
// ... load assets ...
PerformanceMonitor.checkpoint('Assets Loaded');
// ... initialize app ...
PerformanceMonitor.checkpoint('App Initialized');
PerformanceMonitor.end('Page Load');

應避免的錯誤

  1. 忘記關閉分組:未關閉的分組會使後面的所有內容都縮排:
   console.group('Process A');
   // ... logs ...
   // Forgot console.groupEnd()!

   console.log('This will be incorrectly indented');
  1. 嵌套過深:超過 3-4 層後,文字難以閱讀:
   // Too deep!
   console.group('Level 1');
     console.group('Level 2');
       console.group('Level 3');
         console.group('Level 4');
           console.group('Level 5'); // Nobody wants to expand this many levels
  1. 單一專案分組:不要為單一日誌建立群組:
   // Pointless
   console.group('User');
   console.log(user);
   console.groupEnd();

   // Just do this
   console.log('User:', user);
  1. 不使用 groupCollapsed :如果您要記錄大量偵錯訊息,請啟用折疊模式:
   // This will clutter your console
   console.group('Detailed Request Info');
   // ... 50 lines of logs ...
   console.groupEnd();

   // This keeps it clean
   console.groupCollapsed('Detailed Request Info');
   // ... 50 lines of logs ...
   console.groupEnd();

它如何節省除錯時間

想像一下,您正在除錯一個複雜的結帳流程,其中包含支付處理、庫存更新、電子郵件通知和分析追蹤。如果沒有分組,您將看到 100 多個日誌語句混雜在一起。您需要滾動、搜尋,才能弄清楚哪些日誌屬於哪個操作。

就群體而言,你會發現:

  • 📦 訂單處理中(已折疊)

  • 💳 付款(已展開,因為問題出在這裡)

  • 卡片驗證

  • 費用處理 ← 此處出錯

  • 收據生成

  • 📧 電子郵件通知(已折疊)

  • 📊 分析(已折疊)

你立即將注意力集中在支付組上,忽略其他所有內容,幾秒鐘內而不是幾分鐘內就找到了錯誤。


  1. console.dir() - 深入了解物件屬性

它的功能

console.dir()會以互動方式顯示物件的屬性清單。與console.log() 嘗試以「美觀」的方式(尤其是對於 DOM 元素)顯示物件不同,console.dir()`始終顯示 JavaScript 物件及其所有屬性和方法的原始表示形式。

為什麼開發者會忽略它

console.log()對大多數物件都能正常運作,所以人們很少會去探索其他方法。但是, console.log() 對某些類型(例如 DOM 節點、陣列等)有特殊的格式化,有時會隱藏你需要的資訊。console.dir()`則能提供原始的、未經過濾的物件結構。

如何使用它

console.dir(object, options);

options 參數(主要用於 Node.js)可以包含深度、顏色等。在瀏覽器中,它通常只是console.dir(object)

實際程式碼範例

範例 1:DOM 元素

const button = document.querySelector('button');

console.log(button);
// Shows: <button class="btn">Click me</button>
// Looks like HTML, shows the element visually

console.dir(button);
// Shows: button {className: "btn", innerHTML: "Click me", onclick: null, ...}
// Shows all properties and methods of the button object

當你需要查看 DOM 元素上有哪些屬性和方法時,這非常有用

例 2:函數

function greet(name) {
  return `Hello, ${name}!`;
}

console.log(greet);
// Shows: ƒ greet(name) { return `Hello, ${name}!`; }

console.dir(greet);
// Shows all function properties:
// {
//   length: 1,
//   name: "greet",
//   arguments: null,
//   caller: null,
//   prototype: {constructor: ƒ},
//   __proto__: ƒ ()
// }

現在您可以查看函數的長度(參數數量)、函數原型和其他元資料。

範例 3:類別實例

class User {
  constructor(name) {
    this.name = name;
    this.createdAt = new Date();
  }

  greet() {
    return `Hello, ${this.name}`;
  }
}

const user = new User('Alice');

console.log(user);
// User {name: "Alice", createdAt: Mon Dec 02 2024...}

console.dir(user);
// Expandable tree showing:
// - name: "Alice"
// - createdAt: Date object
// - __proto__: User
//   - greet: ƒ greet()
//   - constructor: ƒ User(name)
//   - __proto__: Object

這揭示了原型鏈,準確地顯示了方法的定義位置。

範例 4:帶有額外屬性的陣列

const arr = [1, 2, 3];
arr.customProp = 'custom value';
arr.customMethod = () => 'custom';

console.log(arr);
// Shows: [1, 2, 3]
// Hides the custom properties!

console.dir(arr);
// Shows:
// Array(3)
//   0: 1
//   1: 2
//   2: 3
//   customProp: "custom value"
//   customMethod: ƒ ()
//   length: 3
//   __proto__: Array

範例 5:檢查內建物件

console.dir(document);
// Shows all properties and methods of the document object
// Useful for exploring what's available

console.dir(window.localStorage);
// Shows localStorage's prototype chain and methods

console.dir(Promise);
// Shows Promise constructor properties:
// all, allSettled, any, race, reject, resolve, etc.

專業提示

  1. 用於 API 探索:當使用不熟悉的程式庫時:
   import * as THREE from 'three';
   console.dir(THREE);
   // See all available exports and their structure
  1. 比較 log() 和 dir() :當對物件結構感到困惑時:
   console.log('log:', myObject);
   console.dir(myObject);
   // Compare the outputs to understand what's happening
  1. 檢查事件物件:事件擁有大量屬性:
   button.addEventListener('click', (event) => {
     console.dir(event);
     // See all event properties: target, currentTarget, clientX, clientY, etc.
   });
  1. Node.js 選項:在 Node.js 中,您可以控制深度:
   console.dir(deeplyNestedObject, { depth: null }); // Show everything
   console.dir(deeplyNestedObject, { depth: 2 });    // Limit depth

進階用例

除錯代理和包裝物件

const handler = {
  get(target, prop) {
    console.log(`Getting ${prop}`);
    return target[prop];
  }
};

const proxy = new Proxy({ name: 'Alice' }, handler);

console.log(proxy);    // Might trigger proxy traps
console.dir(proxy);    // Shows proxy structure without triggering traps

檢查自訂迭代器

const range = {
  from: 1,
  to: 5,
  [Symbol.iterator]() {
    return {
      current: this.from,
      last: this.to,
      next() {
        if (this.current <= this.last) {
          return { done: false, value: this.current++ };
        } else {
          return { done: true };
        }
      }
    };
  }
};

console.dir(range);
// Shows the Symbol.iterator method and all properties

理解遺傳

class Animal {
  eat() { return 'eating'; }
}

class Dog extends Animal {
  bark() { return 'woof'; }
}

const dog = new Dog();
console.dir(dog);

// Expandable view shows:
// Dog {}
//   __proto__: Animal
//     bark: ƒ bark()
//     constructor: class Dog
//     __proto__: Object
//       eat: ƒ eat()
//       constructor: class Animal

應避免的錯誤

  1. 不要用於簡單值:對於基本類型來說,這樣做是過度設計的:
   console.dir(42);           // Just shows: 42
   console.dir("hello");      // Just shows: "hello"
   console.log(42);           // Same output, more semantic
  1. 請記住它是唯讀的:您無法編輯console.dir()輸出中的值(就像普通控制台一樣):
   console.dir(user); // Can't click and edit user.name
  1. 不要指望在不同瀏覽器中顯示完全相同的輸出結果:Chrome、Firefox 和 Safari 顯示物件的方式不同。

它如何節省除錯時間

您正在使用第三方函式庫,需要了解有哪些方法可用。您可以閱讀文件(如果存在且為最新版本),或者您可以這樣做:

import { SomeClass } from 'mystery-library';
const instance = new SomeClass();
console.dir(instance);

搞定!所有屬性和方法、原型鏈、繼承屬性——所有的一切盡收眼底。你發現還有個instance.reset()方法,但它並沒有被記錄在文件裡。問題解決了。

或者,你正在除錯 DOM 問題, console.log(element)顯示了漂亮的 HTML 程式碼,但你需要知道它的offsetWidthscrollTop或事件處理程序。 console.dirconsole.dir(element)`可以顯示所有這些資訊。


  1. console.count() 和 console.countReset() - 無需樣板程式碼即可追蹤函數呼叫

他們做什麼

console.count() 會記錄帶有特定標籤的呼叫次數。console.countReset console.countReset()會重置計數器。這就像為你的程式碼內建了一個點擊計數器。

為什麼開發者會忽略它們

大多數開發者會手動使用變數來追蹤計數:

let callCount = 0;
function myFunction() {
  callCount++;
  console.log('Called', callCount, 'times');
}

這種方法雖然可行,但程式碼冗長,而且需要管理狀態。 console.countconsole.count()`可以用一行程式碼完成所有操作,無需額外的變數。

如何使用它們

console.count(label);        // Increments and logs count
console.countReset(label);   // Resets counter to 0

如果您不提供標籤,則使用'default'

實際程式碼範例

例1:基本計數

function handleClick() {
  console.count('button clicks');
  // Do click handling...
}

button.addEventListener('click', handleClick);

// First click:  button clicks: 1
// Second click: button clicks: 2
// Third click:  button clicks: 3

範例 2:追蹤函數呼叫

function fetchData(endpoint) {
  console.count(`API call to ${endpoint}`);
  return fetch(endpoint);
}

fetchData('/users');      // API call to /users: 1
fetchData('/posts');      // API call to /posts: 1
fetchData('/users');      // API call to /users: 2
fetchData('/users');      // API call to /users: 3

這樣就能立即顯示哪些端點被存取的頻率最高。

例 3:除錯無限循環或遞歸

function problematicRecursion(n) {
  console.count('recursion depth');

  if (n <= 0) return;

  // Oops, forgot to decrement!
  problematicRecursion(n);
}

problematicRecursion(5);

// Output:
// recursion depth: 1
// recursion depth: 2
// recursion depth: 3
// ... keeps going ...

你會立即看到計數器不斷上升,並知道你遇到了遞歸問題。

範例 4:React 渲染計數

function UserProfile({ userId }) {
  console.count(`UserProfile render for user ${userId}`);

  // Component logic...
  return <div>Profile</div>;
}

// Output shows exactly how many times and for which users:
// UserProfile render for user 123: 1
// UserProfile render for user 123: 2
// UserProfile render for user 456: 1

範例 5:事件處理程序追蹤

input.addEventListener('input', () => {
  console.count('input event');
});

input.addEventListener('change', () => {
  console.count('change event');
});

// Type "hello":
// input event: 1
// input event: 2
// input event: 3
// input event: 4
// input event: 5
// change event: 1 (when you blur)

專業提示

  1. 使用描述性標籤:使其有意義:
   // Bad
   console.count('x');

   // Good
   console.count('validation-failure');
   console.count('cache-hit');
   console.count('database-query');
  1. 必要時重置:在測試執行之間清除計數:
   function runTest() {
     console.countReset('test-assertion');
     // Run test...
   }

   function assert(condition) {
     if (!condition) {
       console.count('test-assertion');
     }
   }
  1. 結合條件語句:僅統計特定情況:
   function processData(data) {
     if (data.size > 10000) {
       console.count('large-dataset');
     }

     if (data.hasErrors) {
       console.count('data-errors');
     }
   }
  1. 同時追蹤多個指標
   function handleRequest(req) {
     console.count('total-requests');

     if (req.method === 'GET') {
       console.count('get-requests');
     } else if (req.method === 'POST') {
       console.count('post-requests');
     }

     if (req.authenticated) {
       console.count('authenticated-requests');
     } else {
       console.count('anonymous-requests');
     }
   }

進階用例

速率限制檢測

let requestCount = 0;

async function apiCall() {
  console.count('API requests this session');

  requestCount++;
  if (requestCount > 100) {
    console.warn('Approaching rate limit!');
  }

  // Make request...
}

內存洩漏檢測

class ResourceManager {
  constructor() {
    console.count('ResourceManager instances created');
  }

  destroy() {
    console.count('ResourceManager instances destroyed');
  }
}

// If created count far exceeds destroyed count, you have a leak

A/B 測試跟踪

function showFeature(variant) {
  console.count(`feature-variant-${variant}`);

  if (variant === 'A') {
    showVariantA();
  } else {
    showVariantB();
  }
}

// Quick visual check of variant distribution:
// feature-variant-A: 523
// feature-variant-B: 477

偵錯輪詢

let pollCount = 0;
const MAX_POLLS = 10;

async function pollForResult() {
  console.count('poll-attempt');
  pollCount++;

  const result = await checkStatus();

  if (result.complete) {
    console.log(`✓ Completed after ${pollCount} polls`);
    console.countReset('poll-attempt');
    return result;
  }

  if (pollCount < MAX_POLLS) {
    setTimeout(pollForResult, 1000);
  } else {
    console.error('Max polls reached');
  }
}

應避免的錯誤

  1. 請勿用於生產環境指標:這些是開發工具。對於生產環境,請使用專業的分析工具。
   // Development: OK
   console.count('user-signup');

   // Production: Use analytics service
   analytics.track('user-signup');
  1. 請注意標籤區分大小寫
   console.count('MyLabel');
   console.count('mylabel');
   // These are DIFFERENT counters!
  1. 計數器持久化:計數器在函數呼叫之間不會自動重置:
   function processItems(items) {
     items.forEach(item => {
       console.count('item-processed');
     });
     // Counter keeps incrementing across multiple processItems calls
   }

   // If you want to reset:
   function processItems(items) {
     console.countReset('item-processed');
     items.forEach(item => {
       console.count('item-processed');
     });
   }

它如何節省除錯時間

你懷疑某個函數被呼叫的頻率過高。你沒有加入一個計數器變數,初始化它,遞增它,然後記錄它的值,而是直接加入了一行程式碼:

console.count('suspiciousFunction');

立即發現,它被呼叫了 50 次,而它應該只被呼叫一次。 10 秒內就找到了這個 bug。

或者,您正在偵錯事件監聽器,但無法確定兩個監聽器是否都觸發了。在每個監聽器上加上console.count() ` 並觀察輸出。如果您只看到一個監聽器的計數,則表示另一個監聽器沒有正確附加。


  1. console.clear() - 清理控制台

它的功能

console.clear()會清除所有控制台輸出。這就像在開發者工具中點擊“清除”按鈕一樣,只不過是從程式碼中執行此操作。

為什麼開發者會忽略它

大多數開發者都是手動清除控制台,使用瀏覽器的清除按鈕或開發者工具的快速鍵。他們沒有意識到可以透過程式清除控制台,而這種方式可以實現一些強大的除錯模式。

如何使用它

console.clear();

就是這樣。沒有參數,沒有複雜度。

實際程式碼範例

範例 1:重大手術前的清理

function runDiagnostics() {
  console.clear();
  console.log('=== Running System Diagnostics ===');

  checkDatabase();
  checkAPI();
  checkCache();

  console.log('=== Diagnostics Complete ===');
}

現在每次診斷執行都從零開始,因此可以輕鬆查看當前執行的輸出。

範例 2:清晰的頁面導航

// In a Single Page Application
router.on('navigate', (route) => {
  if (process.env.NODE_ENV === 'development') {
    console.clear();
    console.log(`Navigated to: ${route.path}`);
  }
});

每個路由都會獲得最新的控制台輸出,避免不同頁面之間出現混淆。

範例 3:遊戲循環除錯

function gameLoop() {
  // Clear console each frame for real-time debugging
  console.clear();

  console.log('=== Frame', frameCount, '===');
  console.log('Player Position:', player.x, player.y);
  console.log('Enemy Count:', enemies.length);
  console.log('Score:', score);
  console.log('FPS:', calculateFPS());

  requestAnimationFrame(gameLoop);
}

您可以即時查看遊戲狀態,而無需面對控制台的雜亂介面。

範例 4:測試套件重置

function runTestSuite(tests) {
  console.clear();
  console.log('╔════════════════════════════╗');
  console.log('║   Test Suite Starting      ║');
  console.log('╚════════════════════════════╝');

  tests.forEach(test => {
    try {
      test();
      console.log(`✓ ${test.name}`);
    } catch (error) {
      console.error(`✗ ${test.name}:`, error);
    }
  });
}

範例 5:開發模式控制台管理

class DevConsole {
  static enabled = process.env.NODE_ENV === 'development';

  static section(title) {
    if (!this.enabled) return;

    console.clear();
    console.log('═'.repeat(50));
    console.log(title.toUpperCase().padStart(25 + title.length / 2));
    console.log('═'.repeat(50));
  }
}

// Usage
DevConsole.section('User Authentication');
console.log('Checking credentials...');
// ... auth logic ...

DevConsole.section('Loading Dashboard');
console.log('Fetching user data...');
// ... dashboard logic ...

專業提示

  1. 條件性清算:僅在開發階段清算:
   if (process.env.NODE_ENV === 'development') {
     console.clear();
   }
  1. 保留重要日誌:清除記錄關鍵資訊:
   console.clear();
   console.log('App Version:', APP_VERSION);
   console.log('Environment:', process.env.NODE_ENV);
   console.log('User ID:', currentUser.id);
   console.log('─'.repeat(50));
   // Now start your actual logging
  1. 在長時間操作中明確要點
   async function longProcess() {
     console.clear();
     console.log('Phase 1: Data Collection');
     await collectData();

     console.clear();
     console.log('Phase 2: Processing');
     await processData();

     console.clear();
     console.log('Phase 3: Output');
     await generateOutput();
   }
  1. 鍵盤快速鍵替換:綁定到某個按鍵以快速清除:
   window.addEventListener('keydown', (e) => {
     if (e.ctrlKey && e.key === 'l') {
       e.preventDefault();
       console.clear();
       console.log('Console cleared manually');
     }
   });

進階用例

即時資料監控

class Monitor {
  constructor(interval = 1000) {
    this.metrics = {};
    this.interval = interval;
  }

  start() {
    this.timer = setInterval(() => {
      console.clear();
      console.log('═══ LIVE METRICS ═══');
      console.log('Updated:', new Date().toLocaleTimeString());
      console.log('');
      console.table(this.metrics);
    }, this.interval);
  }

  update(key, value) {
    this.metrics[key] = value;
  }

  stop() {
    clearInterval(this.timer);
  }
}

const monitor = new Monitor();
monitor.start();

// Somewhere in your app:
monitor.update('Active Users', getActiveUsers());
monitor.update('Memory Usage', getMemoryUsage());
monitor.update('API Latency', getAPILatency());

互動式除錯選單

const debugMenu = {
  showState() {
    console.clear();
    console.log('📊 APPLICATION STATE');
    console.log('User:', currentUser);
    console.log('Route:', currentRoute);
    console.log('Store:', store.getState());
  },

  showPerformance() {
    console.clear();
    console.log('⚡ PERFORMANCE METRICS');
    console.table(performance.getEntries());
  },

  showNetwork() {
    console.clear();
    console.log('🌐 NETWORK REQUESTS');
    // Log network stats
  }
};

// Access from browser console:
// debugMenu.showState()
// debugMenu.showPerformance()

動畫控制台輸出

const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
let frameIndex = 0;

function showLoader(message) {
  const interval = setInterval(() => {
    console.clear();
    console.log(`${frames[frameIndex]} ${message}`);
    frameIndex = (frameIndex + 1) % frames.length;
  }, 80);

  return () => {
    clearInterval(interval);
    console.clear();
  };
}

// Usage
const stopLoader = showLoader('Loading data...');
await fetchData();
stopLoader();
console.log('✓ Data loaded');

應避免的錯誤

  1. 不要在生產環境中使用 `console.clear ():從生產版本移除console.clear()`。
   // This will annoy users debugging your site
   setInterval(() => console.clear(), 1000); // DON'T DO THIS
  1. 不要清除錯誤訊息:請小心不要清除重要的錯誤訊息:
   try {
     riskyOperation();
   } catch (error) {
     console.error(error); // Log the error first
     console.clear();       // Now it's gone! Bad!
   }
  1. 過度清除 = 控制台無法讀取:請勿過於頻繁地清除:
   // Bad: clears every iteration
   for (let i = 0; i < 100; i++) {
     console.clear();
     console.log(i); // Too fast to read!
   }
  1. 瀏覽器限制:部分瀏覽器會顯示「控制台已清除」訊息。用戶可能會覺得這很煩人。

它如何節省除錯時間

在偵錯包含多個步驟的複雜工作流程時,您絕對不想捲動瀏覽先前執行留下的數百行日誌。在偵錯會話開始時console.clear()可以清除所有日誌,讓您重新開始。

或想像一下除錯即時功能,例如聊天應用程式或即時資訊流。舊訊息會堵塞控制台。在記錄新訊息之前加入console.clear()可以保持控制台內容清晰易讀。

我在除錯動畫循環或遊戲邏輯時經常使用這個功能,因為我需要查看當前狀態,而不需要查看之前每一幀的日誌。


  1. console.warn() 和 console.error() - 真正重要的語意日誌記錄

他們做什麼

console.warn() 記錄警告訊息(大多數瀏覽器中顯示為黃色),而console.error()` 記錄錯誤訊息(顯示為紅色)。它們的運作方式與console.log()`類似,但視覺樣式和語意意義不同。

為什麼開發者會忽略它們

許多開發者甚麼都用console.log() 。他們認為樣式無關緊要,或者根本沒意識到瀏覽器對這些方法的處理方式不同。但使用正確的方法可以提高程式碼可讀性,支援過濾,並提供更清晰的堆疊追蹤資訊。

如何使用它們

console.warn(message, ...optionalData);
console.error(message, ...optionalData);

兩者都像console.log()一樣接受多個參數。

實際程式碼範例

範例 1:棄用警告

function oldAPIMethod(data) {
  console.warn(
    'oldAPIMethod() is deprecated and will be removed in v3.0. Use newAPIMethod() instead.',
    'Called with:', data
  );

  // Still execute for backward compatibility
  return legacyLogic(data);
}

黃色警告燈能引起注意,但不會阻止執行。

範例 2:設定問題

function initializeApp(config) {
  if (!config.apiKey) {
    console.error('CRITICAL: API key is missing. App will not function properly.');
    throw new Error('Missing API key');
  }

  if (!config.analyticsId) {
    console.warn('WARNING: Analytics ID not provided. Analytics will be disabled.');
  }

  if (config.debugMode) {
    console.warn('App running in debug mode. Performance may be degraded.');
  }

  // Initialize...
}

錯誤以紅色顯示,屬於嚴重錯誤。警告以黃色顯示,屬於提示訊息。

範例 3:驗證錯誤

function validateUser(user) {
  const errors = [];
  const warnings = [];

  if (!user.email) {
    errors.push('Email is required');
  } else if (!isValidEmail(user.email)) {
    errors.push('Email format is invalid');
  }

  if (!user.phone) {
    warnings.push('Phone number is recommended but optional');
  }

  if (user.age < 13) {
    errors.push('User must be 13 or older');
  } else if (user.age < 18) {
    warnings.push('User is under 18 - some features restricted');
  }

  if (errors.length > 0) {
    console.error('User validation failed:', errors);
    return false;
  }

  if (warnings.length > 0) {
    console.warn('User validation warnings:', warnings);
  }

  return true;
}

範例 4:效能警告

function processLargeDataset(data) {
  if (data.length > 10000) {
    console.warn(
      `Processing ${data.length} items may impact performance.`,
      'Consider using pagination or virtual scrolling.'
    );
  }

  const start = performance.now();
  const result = process(data);
  const duration = performance.now() - start;

  if (duration > 1000) {
    console.error(
      `SLOW OPERATION: Processing took ${duration}ms`,
      'This will cause UI jank'
    );
  }

  return result;
}

範例 5:API 錯誤處理

async function fetchUserData(userId) {
  try {
    const response = await fetch(`/api/users/${userId}`);

    if (!response.ok) {
      if (response.status === 404) {
        console.error(`User ${userId} not found`);
      } else if (response.status >= 500) {
        console.error(`Server error (${response.status}) when fetching user`);
      } else {
        console.warn(`Unexpected response: ${response.status}`);
      }

      throw new Error(`HTTP ${response.status}`);
    }

    return await response.json();

  } catch (error) {
    console.error('Failed to fetch user data:', error);
    throw error;
  }
}

專業提示

  1. 堆疊追蹤:在大多數瀏覽器中, console.error()會自動包含堆疊追蹤:
   function deepFunction() {
     console.error('Something went wrong here');
     // Browser shows full call stack automatically
   }
  1. 按類型篩選:開發者工具可讓您按類型篩選控制台輸出。使用正確的方法可以有效進行篩選:
   // In DevTools, click "Errors" to see only these
   console.error('Critical issue');
   console.error('Database connection failed');

   // Click "Warnings" to see only these
   console.warn('Deprecated method used');
   console.warn('Low memory warning');
  1. 新增上下文物件:包含用於除錯的相關資料:
   console.error('Failed to save user', {
     userId: user.id,
     attemptedData: data,
     timestamp: new Date(),
     sessionId: getSessionId()
   });
  1. 與錯誤物件一起使用:傳遞實際的錯誤物件以獲得更清晰的堆疊追蹤:
   try {
     riskyOperation();
   } catch (error) {
     console.error('Operation failed:', error);
     // Shows error message AND stack trace
   }

進階用例

自訂錯誤等級

const Logger = {
  ERROR: 'error',
  WARN: 'warn',
  INFO: 'info',
  DEBUG: 'debug',

  log(level, message, data) {
    const timestamp = new Date().toISOString();
    const prefix = `[${timestamp}] [${level.toUpperCase()}]`;

    switch(level) {
      case this.ERROR:
        console.error(prefix, message, data);
        // Send to error tracking service
        sendToErrorTracking(message, data);
        break;
      case this.WARN:
        console.warn(prefix, message, data);
        break;
      default:
        console.log(prefix, message, data);
    }
  },

  error(message, data) { this.log(this.ERROR, message, data); },
  warn(message, data) { this.log(this.WARN, message, data); },
  info(message, data) { this.log(this.INFO, message, data); },
  debug(message, data) { this.log(this.DEBUG, message, data); }
};

// Usage
Logger.error('Payment processing failed', { orderId: 123, amount: 99.99 });
Logger.warn('API rate limit approaching', { remaining: 10, limit: 100 });

錯誤邊界日誌記錄(React)

class ErrorBoundary extends React.Component {
  componentDidCatch(error, errorInfo) {
    console.error('React Error Boundary caught an error:', {
      error: error.toString(),
      componentStack: errorInfo.componentStack,
      timestamp: new Date().toISOString()
    });

    // Could also send to error tracking service
  }

  render() {
    if (this.state.hasError) {
      return <ErrorFallback />;
    }
    return this.props.children;
  }
}

漸進式錯誤升級

class APIClient {
  constructor() {
    this.retryCount = 0;
    this.maxRetries = 3;
  }

  async request(url) {
    try {
      const response = await fetch(url);

      if (!response.ok) {
        this.retryCount++;

        if (this.retryCount === 1) {
          console.warn(`Request failed (attempt ${this.retryCount}), retrying...`);
        } else if (this.retryCount < this.maxRetries) {
          console.warn(`Request failed (attempt ${this.retryCount}/${this.maxRetries})`);
        } else {
          console.error(`Request failed after ${this.maxRetries} attempts`, {
            url,
            status: response.status
          });
          throw new Error('Max retries exceeded');
        }

        await this.delay(1000 * this.retryCount);
        return this.request(url);
      }

      this.retryCount = 0; // Reset on success
      return response;

    } catch (error) {
      console.error('Network error:', error);
      throw error;
    }
  }

  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

環境感知日誌記錄

const isDev = process.env.NODE_ENV === 'development';
const isTest = process.env.NODE_ENV === 'test';

const logger = {
  error(...args) {
    console.error(...args);
    if (!isDev && !isTest) {
      sendToErrorTracking(...args);
    }
  },

  warn(...args) {
    if (isDev) {
      console.warn(...args);
    }
  },

  info(...args) {
    if (isDev) {
      console.log(...args);
    }
  }
};

// Now warnings and info only appear in development
logger.warn('This API is slow'); // Dev only
logger.error('Critical failure'); // Always logged + tracked in production

智慧錯誤分類

class ErrorHandler {
  static handle(error, context = {}) {
    // Network errors
    if (error instanceof TypeError && error.message.includes('fetch')) {
      console.error('Network Error:', {
        message: 'Could not connect to server',
        context,
        originalError: error
      });
      return;
    }

    // Validation errors
    if (error.name === 'ValidationError') {
      console.warn('Validation Error:', {
        message: error.message,
        context
      });
      return;
    }

    // Permission errors
    if (error.status === 403) {
      console.error('Permission Denied:', {
        message: 'User lacks required permissions',
        context
      });
      return;
    }

    // Unknown errors
    console.error('Unexpected Error:', {
      error,
      context,
      stack: error.stack
    });
  }
}

// Usage
try {
  await saveData(data);
} catch (error) {
  ErrorHandler.handle(error, { userId: currentUser.id, action: 'save' });
}

應避免的錯誤

  1. 不要對非錯誤使用 error()
   // Bad - this isn't an error
   console.error('User clicked button');

   // Good
   console.log('User clicked button');
  1. 不要過度使用 warn() 函數
   // Too many warnings = noise
   console.warn('Processing...');
   console.warn('Step 1 complete');
   console.warn('Step 2 complete');

   // Just use log() for normal flow
   console.log('Processing...');
  1. 不要默默地吞下錯誤
   // Bad - error is caught but not logged
   try {
     await criticalOperation();
   } catch (error) {
     // Nothing - error disappears!
   }

   // Good
   try {
     await criticalOperation();
   } catch (error) {
     console.error('Critical operation failed:', error);
     throw error; // Or handle appropriately
   }
  1. 不要記錄敏感資料
   // Dangerous - logs user password
   console.error('Login failed', { username, password });

   // Safe
   console.error('Login failed', { username });

它如何節省除錯時間

視覺區分非常重要。當控制台有 100 個日誌語句時,鮮紅色的錯誤訊息會立即被反白。您無需浪費時間閱讀普通日誌來尋找問題。

篩選功能更加強大。在開發者工具中點擊“僅錯誤”,即可立即查看所有問題。點擊“警告”,即可查看潛在問題,而無需查看嚴重故障。

此外,像 Sentry 這樣的錯誤追蹤服務會自動擷取生產環境中的console.error()呼叫。正確使用console.error()意味著您的生產錯誤會自動記錄到監控系統中,無需編寫額外的程式碼。


  1. console.memory - 即時監控記憶體使用情況

它的功能

console.memory (在基於 Chrome 的瀏覽器中)提供有關 JavaScript 堆記憶體使用情況的即時資訊。它不是一個方法,而是一個屬性,該屬性傳回一個包含記憶體統計資訊的物件。

為什麼開發者會忽略它

這是 Chrome 特有的,並非標準化的,因此不太為人所知。此外,大多數開發者只有在遇到記憶體洩漏時才會考慮記憶體問題。但主動監控記憶體有助於在洩漏演變成問題之前將其發現。

如何使用它

console.log(console.memory);

// Returns something like:
// {
//   jsHeapSizeLimit: 2190000000,  // Max heap size
//   totalJSHeapSize: 50000000,    // Total allocated
//   usedJSHeapSize: 30000000      // Actually used
// }

所有數值均以位元組為單位。

實際程式碼範例

範例 1:基本記憶體檢查


function checkMemory() {
  const memory = console.memory;
  const used = (memory.usedJSHeapSize / 1048576).toFixed(2);
  const limit = (memory.jsHeapSizeLimit / 1048576).toFixed(2);
  const percentage = ((memory.usedJSHeapSize / memory.jsHeapSizeLimit) * 100).toFixed(2);

  console.log(`Memory: ${used} MB / ${limit} MB (${percentage}%)`);
}

checkMemory();
// Outp

精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝15   💬3   ❤️3
315
🥈
我愛JS
📝1   💬3   ❤️2
45
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付