說實話,在我從事 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,更能理解效能瓶頸,而且說實話,你會感覺自己解鎖了一種超能力。
那麼,讓我們深入了解十個大多數開發者不知道存在,但絕對應該了解的控制台方法。
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() 。
console.table()來查看轉換結果: console.table(
users
.filter(u => u.active)
.map(u => ({ name: u.name, role: u.role }))
);
console.table(users.sort((a, b) => a.name.localeCompare(b.name)));
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()可以讓查看查詢結果變得比查看原始日誌容易得多。
不要用於處理海量陣列:在表格中顯示 10,000 行資料會導致瀏覽器卡頓。請先篩選資料。
注意循環引用:如果你的物件存在循環引用, console.table()可能無法正確處理。 Chrome 通常可以處理,但 Firefox 可能會出錯。
請記住,它是唯讀的:您無法編輯控制台表格中的值並將其反映到您的程式碼中。它僅用於可視化。
想像一下,你要除錯一個處理使用者資料的函數。使用console.log() ,你會看到類似這樣的輸出:
[{id: 1, name: "Sarah", ...}, {id: 2, name: "Marcus", ...}, ...]
你需要點擊才能展開、滾動,還要在腦海中比較數值。而使用console.table() ,所有內容一目了然。你可以立即發現使用者 ID 7 的郵件地址為空,或有三個使用者的電話號碼格式錯誤。原本需要點擊和滾動五分鐘才能完成的工作,現在只需五秒鐘就能搞定。
這兩個方法配合使用。 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 運作緩慢了。
使用描述性標籤:不要使用像“timer1”或“test”這樣的通用標籤。請使用'api-fetch-user-profile'或'sort-10k-products' 。未來的你會感謝我的。
配合 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
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');
});
忘記呼叫 timeEnd() :如果忘記呼叫,計時器會一直保持開啟。不會報錯,也不會發出警告。務必確保它們始終配對使用。
標籤不符: console.time('myTimer')和console.timeEnd('mytimer')無法正常運作。 JavaScript 區分大小寫。
請勿用於生產環境監控:這些是開發工具。對於生產環境效能監控,請使用專業的 APM 工具,例如 New Relic、Datadog 或具有分析功能的效能 API。
避免在不使用 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()`更簡潔、更易讀,也更容易實現。
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
現在您可以看到確切的路徑: handleCheckout → processOrder → calculateTotal 。
範例 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
console.trace(`User ${userId} reached checkout`);
function updateCart(item) {
if (!item.price) {
console.trace('Item has no price - this should never happen');
}
// rest of code
}
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);
};
不要將它們留在生產環境中:堆疊追蹤會降低效能。發布前請將其移除。
注意瀏覽器差異:Chrome、Firefox 和 Safari 的堆疊追蹤格式各不相同。不要依賴程式自動解析輸出。
非同步堆疊追蹤可能具有誤導性:使用 async/await 和 Promise 時,堆疊追蹤可能無法顯示全部情況。現代瀏覽器雖然支援非同步堆疊追蹤,但並非總是完美無缺。
過多的追蹤資訊等於雜訊:如果追蹤所有內容,輸出結果將淹沒在海量資訊中。要精準控制。
你遇到了一個 bug,某個值被錯誤地設定了,但你不知道錯在哪裡。你可以到處加入console.log()語句,也可以只加入一條console.trace()語句,在錯誤值被設定的地方記錄一次,就能立即看到呼叫路徑。
我曾經除錯過一個 Redux action,它被從一個意想不到的地方分發出去。與其在成千上萬行程式碼中苦苦搜尋,不如直接在 reducer 中加入console.trace() 。堆疊追蹤直接指向了一個我不知道的第三方函式庫,它正在分發我之前不知道的 action。這幫我節省了幾個小時。
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 }
);
}
}
console.assert(
user.age >= 18,
'User must be 18+',
{ user, currentAge: user.age, required: 18 }
);
function updateProfile(userId, updates) {
console.assert(userId, 'userId is required');
console.assert(Object.keys(updates).length > 0, 'updates cannot be empty');
// Function logic...
}
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...
}
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
console.assert(userId = getUserId(), 'No user ID'); // Assignment in assertion!
0 、 ''和null都是假值: console.assert(count, 'Count is required'); // Fails when count is 0!
console.assert(count !== undefined, 'Count is required'); // Better
斷言充當執行時文件和預警系統。它不會讓神秘的錯誤在程式碼深處(例如三層函數之後)才顯現出來,而是在入口點捕獲錯誤資料,並準確地告訴你哪裡出了問題。
我在開發過程中大量使用斷言。它們幫我發現了很多 bug,像是參數傳遞順序錯誤、忘記處理邊界情況,或是對資料結構做出錯誤的假設。斷言會立即在問題根源處失敗,而不是在之後拋出晦澀難懂的錯誤訊息。
這些方法允許你在控制台輸出中建立可折疊的分組。 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();
}
}
console.groupCollapsed('Detailed Debug Info');
console.log('Memory usage:', performance.memory.usedJSHeapSize);
console.log('Network status:', navigator.onLine);
console.groupEnd();
console.group('Critical Operation');
try {
riskyOperation();
} finally {
console.groupEnd(); // Always closes even if error is thrown
}
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');
console.group('Process A');
// ... logs ...
// Forgot console.groupEnd()!
console.log('This will be incorrectly indented');
// 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
// Pointless
console.group('User');
console.log(user);
console.groupEnd();
// Just do this
console.log('User:', user);
// 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 多個日誌語句混雜在一起。您需要滾動、搜尋,才能弄清楚哪些日誌屬於哪個操作。
就群體而言,你會發現:
📦 訂單處理中(已折疊)
💳 付款(已展開,因為問題出在這裡)
卡片驗證
費用處理 ← 此處出錯
收據生成
📧 電子郵件通知(已折疊)
📊 分析(已折疊)
你立即將注意力集中在支付組上,忽略其他所有內容,幾秒鐘內而不是幾分鐘內就找到了錯誤。
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.
import * as THREE from 'three';
console.dir(THREE);
// See all available exports and their structure
console.log('log:', myObject);
console.dir(myObject);
// Compare the outputs to understand what's happening
button.addEventListener('click', (event) => {
console.dir(event);
// See all event properties: target, currentTarget, clientX, clientY, etc.
});
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
console.dir(42); // Just shows: 42
console.dir("hello"); // Just shows: "hello"
console.log(42); // Same output, more semantic
console.dir()輸出中的值(就像普通控制台一樣): console.dir(user); // Can't click and edit user.name
您正在使用第三方函式庫,需要了解有哪些方法可用。您可以閱讀文件(如果存在且為最新版本),或者您可以這樣做:
import { SomeClass } from 'mystery-library';
const instance = new SomeClass();
console.dir(instance);
搞定!所有屬性和方法、原型鏈、繼承屬性——所有的一切盡收眼底。你發現還有個instance.reset()方法,但它並沒有被記錄在文件裡。問題解決了。
或者,你正在除錯 DOM 問題, console.log(element)顯示了漂亮的 HTML 程式碼,但你需要知道它的offsetWidth 、 scrollTop或事件處理程序。 console.dirconsole.dir(element)`可以顯示所有這些資訊。
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)
// Bad
console.count('x');
// Good
console.count('validation-failure');
console.count('cache-hit');
console.count('database-query');
function runTest() {
console.countReset('test-assertion');
// Run test...
}
function assert(condition) {
if (!condition) {
console.count('test-assertion');
}
}
function processData(data) {
if (data.size > 10000) {
console.count('large-dataset');
}
if (data.hasErrors) {
console.count('data-errors');
}
}
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');
}
}
// Development: OK
console.count('user-signup');
// Production: Use analytics service
analytics.track('user-signup');
console.count('MyLabel');
console.count('mylabel');
// These are DIFFERENT counters!
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() ` 並觀察輸出。如果您只看到一個監聽器的計數,則表示另一個監聽器沒有正確附加。
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 ...
if (process.env.NODE_ENV === 'development') {
console.clear();
}
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
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();
}
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');
:從生產版本移除console.clear()`。 // This will annoy users debugging your site
setInterval(() => console.clear(), 1000); // DON'T DO THIS
try {
riskyOperation();
} catch (error) {
console.error(error); // Log the error first
console.clear(); // Now it's gone! Bad!
}
// Bad: clears every iteration
for (let i = 0; i < 100; i++) {
console.clear();
console.log(i); // Too fast to read!
}
在偵錯包含多個步驟的複雜工作流程時,您絕對不想捲動瀏覽先前執行留下的數百行日誌。在偵錯會話開始時console.clear()可以清除所有日誌,讓您重新開始。
或想像一下除錯即時功能,例如聊天應用程式或即時資訊流。舊訊息會堵塞控制台。在記錄新訊息之前加入console.clear()可以保持控制台內容清晰易讀。
我在除錯動畫循環或遊戲邏輯時經常使用這個功能,因為我需要查看當前狀態,而不需要查看之前每一幀的日誌。
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;
}
}
console.error()會自動包含堆疊追蹤: function deepFunction() {
console.error('Something went wrong here');
// Browser shows full call stack automatically
}
// 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');
console.error('Failed to save user', {
userId: user.id,
attemptedData: data,
timestamp: new Date(),
sessionId: getSessionId()
});
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' });
}
// Bad - this isn't an error
console.error('User clicked button');
// Good
console.log('User clicked button');
// 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...');
// 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
}
// Dangerous - logs user password
console.error('Login failed', { username, password });
// Safe
console.error('Login failed', { username });
視覺區分非常重要。當控制台有 100 個日誌語句時,鮮紅色的錯誤訊息會立即被反白。您無需浪費時間閱讀普通日誌來尋找問題。
篩選功能更加強大。在開發者工具中點擊“僅錯誤”,即可立即查看所有問題。點擊“警告”,即可查看潛在問題,而無需查看嚴重故障。
此外,像 Sentry 這樣的錯誤追蹤服務會自動擷取生產環境中的console.error()呼叫。正確使用console.error()意味著您的生產錯誤會自動記錄到監控系統中,無需編寫額外的程式碼。
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