本文 AI 產出,尚未審核

JavaScript 錯誤與除錯:深入瞭解 Error 物件namemessagestack


簡介

在 JavaScript 開發過程中,錯誤(Error) 是不可避免的。無論是語法錯誤、執行時例外,或是自訂業務例外,正確地取得錯誤資訊是定位問題、提升程式品質的關鍵。
Error 物件本身提供了三個最常被使用的屬性:namemessagestack。掌握它們的意義與用法,能讓你在 除錯(debugging) 時快速定位問題根源,亦能在 錯誤處理(error handling) 時提供使用者友善的回饋。

本篇文章將從概念說明、實作範例、常見陷阱與最佳實踐,帶你一步步熟悉這三個屬性,並示範它們在真實專案中的應用方式。文章以繁體中文(台灣)撰寫,適合 初學者到中級開發者 閱讀,並配合大量程式碼範例,讓你立即上手。


核心概念

1. Error 物件的基本結構

在 JavaScript 中,所有錯誤皆繼承自全域的 Error 建構子。最簡單的建立方式如下:

const err = new Error('發生未知錯誤');
console.log(err);

執行結果會顯示一個 Error 物件,包含以下預設屬性:

屬性 型別 說明
name string 錯誤類型的名稱,預設為 "Error"
message string 錯誤的描述訊息,由建構子參數傳入
stack string 堆疊追蹤(stack trace),顯示錯誤發生時的呼叫路徑(瀏覽器或 Node.js 會自動產生)

stack 屬性不是 ECMAScript 標準的一部份,但在主流環境(Chrome、Firefox、Node)皆支援,且在除錯時最有價值。


2. name:辨識錯誤類型

name 用來說明錯誤的類別,預設為 "Error",但你可以透過子類別或自行修改來區分不同情境:

// 內建子類別
try {
  JSON.parse('不是 JSON');
} catch (e) {
  console.log(e.name); // SyntaxError
}

// 自訂錯誤類別
class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = 'ValidationError'; // 明確指定類型名稱
  }
}

實務意義

  • catch 區塊裡可根據 name 做條件分支,提供不同的錯誤處理策略。
  • 日誌系統(logging)常利用 name 來篩選與統計錯誤類型。

3. message:錯誤的說明文字

message 是開發者自行提供的描述,應該 具體、易讀,讓維護者或使用者能快速了解問題。

function divide(a, b) {
  if (b === 0) {
    throw new Error('除數不可為 0');
  }
  return a / b;
}

最佳實踐

  • 避免使用過於簡短的訊息(如 "錯誤"),也不要直接暴露內部實作細節(如資料庫連線字串)。
  • 若錯誤與參數相關,可把參數值寫入 message,提升可追溯性。
throw new Error(`除數不可為 0,實際收到的值為 ${b}`);

4. stack:堆疊追蹤的力量

stack 會列出錯誤拋出時的呼叫路徑,通常包含檔案名稱、行號與函式名稱。它是 除錯的金礦,尤其在大型應用程式中,能快速定位是哪一段程式碼拋出了例外。

function foo() {
  bar();
}
function bar() {
  baz();
}
function baz() {
  throw new Error('故意拋出錯誤');
}
try {
  foo();
} catch (e) {
  console.log(e.stack);
}

執行結果(Chrome):

Error: 故意拋出錯誤
    at baz (example.js:9:9)
    at bar (example.js:5:3)
    at foo (example.js:1:3)
    at <anonymous> (example.js:13:3)

實務應用

  • 日誌系統:將 stack 寫入檔案或遠端服務,以便事後分析。
  • 錯誤上報:在前端應用中,把 stack 送到後端的錯誤監控平台(如 Sentry、BugSnag)。
  • 自訂錯誤類別:在子類別中保留或重新格式化 stack,讓開發者看到更清晰的呼叫層級。

程式碼範例

以下提供 5 個實用範例,示範如何在不同情境下使用 namemessagestack

範例 1:基本的 try…catch 與屬性讀取

try {
  // 故意觸發 ReferenceError
  nonExistentVariable;
} catch (err) {
  console.log('錯誤名稱 :', err.name);      // ReferenceError
  console.log('錯誤訊息 :', err.message);   // nonExistentVariable is not defined
  console.log('堆疊資訊 :\n', err.stack);   // 包含檔案與行號
}

重點:即使是原生錯誤,namemessagestack 仍可直接取得。


範例 2:自訂錯誤類別並加入額外資訊

class ApiError extends Error {
  constructor(status, url, message) {
    super(message);
    this.name = 'ApiError';
    this.status = status;   // HTTP 狀態碼
    this.url = url;         // 請求的 URL
    // 讓 stack 更有意義(只保留自訂層級)
    if (Error.captureStackTrace) {
      Error.captureStackTrace(this, ApiError);
    }
  }
}

// 使用範例
function fetchData(endpoint) {
  // 假設收到 404 錯誤
  throw new ApiError(404, endpoint, '找不到資源');
}

try {
  fetchData('/api/users/123');
} catch (e) {
  console.error(`[${e.name}] ${e.message} (status: ${e.status})`);
  console.error('發生於 URL:', e.url);
  console.error('堆疊資訊:\n', e.stack);
}

說明:自訂錯誤類別可以保留額外屬性(如 statusurl),同時仍保有 namemessagestack 的完整資訊,方便後續的錯誤上報與分析。


範例 3:在 Node.js 中捕獲非同步錯誤

const fs = require('fs').promises;

async function readConfig() {
  try {
    const data = await fs.readFile('./config.json', 'utf8');
    return JSON.parse(data);
  } catch (err) {
    // 可能是檔案不存在、JSON 解析錯誤或其他 IO 錯誤
    console.error('錯誤類型:', err.name);
    console.error('錯誤訊息:', err.message);
    console.error('堆疊追蹤:\n', err.stack);
    // 重新拋出自訂錯誤,讓上層決定如何處理
    throw new Error('讀取設定檔失敗');
  }
}

readConfig().catch(e => console.error('最外層捕獲:', e.message));

關鍵:即使在 async/await 流程中,catch 仍能取得完整的 stack,有助於定位非同步錯誤的根本來源。


範例 4:利用 stack 產生友善的錯誤訊息

function formatError(err) {
  // 只保留前兩層呼叫資訊,避免過長
  const stackLines = err.stack.split('\n').slice(0, 3);
  return `${err.name}: ${err.message}\n於 ${stackLines.join('\n')}`;
}

function a() { b(); }
function b() { c(); }
function c() { throw new Error('發生意外'); }

try {
  a();
} catch (e) {
  console.log(formatError(e));
}

輸出:

Error: 發生意外
於 Error: 發生意外
    at c (<anonymous>:4:9)
    at b (<anonymous>:3:3)

實務應用:在前端 UI 中顯示簡短的錯誤資訊給使用者,同時在後端保留完整 stack 供開發者除錯。


範例 5:全局錯誤監聽與自動上報

// 前端全局錯誤處理(window.onerror)
window.onerror = function (msg, src, line, col, error) {
  const payload = {
    name: error?.name || 'Error',
    message: msg,
    stack: error?.stack || `at ${src}:${line}:${col}`
  };
  // 假設有一個上報 API
  fetch('/api/reportError', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(payload)
  });
  // 防止錯誤再次冒泡
  return true;
};

說明:透過全局監聽,我們可以在任何未被捕獲的錯誤發生時,取得 namemessagestack,並自動上傳至後端做集中監控。


常見陷阱與最佳實踐

陷阱 說明 最佳做法
未設定 name 自訂錯誤類別忘記改寫 this.name,會被視為一般 Error 在子類別建構子裡明確指定 this.name
stack 失真 某些瀏覽器(如 IE)不支援 stack,或在壓縮後的程式碼中行號不正確。 使用 source map(.map)讓壓縮檔的 stack 仍能對應到原始檔。
過度暴露資訊 把機密資訊(如資料庫帳號)寫入 messagestack,會在前端或日誌中洩漏。 僅在開發環境保留完整堆疊,正式環境只上傳錯誤代碼或簡短訊息。
忽略非同步錯誤 try…catch 只能捕捉同步錯誤,Promiseasync/await 需要額外處理。 為每個 Promise 加上 .catch(),或在 async 函式內使用 try…catch
直接拋出字串 throw 'error' 只會得到字串,缺少 namestack,不易除錯。 永遠拋出 Error 或其子類別的實例。

總結最佳實踐

  1. 統一使用 Error 物件:自訂錯誤時繼承 Error,確保 stack 正確生成。
  2. 明確命名 name:讓錯誤類型一目了然,方便條件分支與日誌分析。
  3. 提供具體且安全的 message:描述問題所在,避免洩漏敏感資訊。
  4. 保存完整 stack:在開發環境或除錯時完整記錄,正式環境可視需求截斷。
  5. 結合 source map:讓壓縮後的程式碼仍能回溯到原始行號。

實際應用場景

1. 前端表單驗證

function validateEmail(email) {
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  if (!emailRegex.test(email)) {
    throw new ValidationError(`電子郵件格式不正確:${email}`);
  }
}

catch 中根據 e.name === 'ValidationError' 直接顯示友善訊息給使用者,而把 e.stack 上報給後端進行統計。

2. 伺服器端 API 錯誤統計

在 Node.js 中,使用中間件捕獲所有未處理的例外,記錄 namemessagestack,並依 name 分類存入資料庫,方便日後產生錯誤報表。

app.use((err, req, res, next) => {
  logger.error({
    name: err.name,
    message: err.message,
    stack: err.stack,
    path: req.path,
    method: req.method
  });
  res.status(500).json({ error: '系統發生錯誤,請稍後再試' });
});

3. 客戶端自動上報(Sentry)

Sentry.init({ dsn: 'https://example@sentry.io/12345' });

window.onerror = (msg, src, line, col, error) => {
  Sentry.captureException(error || new Error(msg));
};

Sentry 會自動擷取 namemessagestack,並顯示在儀表板上,協助團隊快速定位問題。


總結

Error 物件的 namemessagestack 三大屬性是 JavaScript 錯誤處理與除錯的核心資訊。

  • name 幫助我們辨識錯誤類型,適合在 catch 中做條件分支。
  • message 應提供具體且安全的描述,讓使用者與開發者都能快速了解問題。
  • stack 則是除錯的金礦,配合 source map 能在壓縮程式碼中仍保有可讀的行號資訊。

透過 自訂錯誤類別全局錯誤監聽日誌與上報機制,我們能將錯誤資訊系統化、可追溯,進而提升程式的穩定性與使用者體驗。

掌握這些概念後,你將能在開發過程中更從容地面對各種例外,並在專案規模擴大時仍保持良好的錯誤管理與除錯效率。祝你寫程式順利,錯誤處理無懈可擊! 🚀