iT邦幫忙

第 12 屆 iT 邦幫忙鐵人賽

DAY 7
9
Modern Web

你所不知道的各種前端 Debug 技巧系列 第 7

[Day 07] Console - API

Debug JavsScript 的時候最簡單直覺的方式就是 console.log,不過除了 log 以外,Console API 其實還有非常多好用的 Method,來看看 Console 家族中有哪些厲害的成員吧。

Console API

大部分的時候 console.log 都能解決問題,不過用對 Method 可以省去更多時間。

console.assert

它的作用和 console.log 差不多,最大的差別是當第一個參數是 falsy 時才會作用。

[false, null, undefined, 0, -0, 0n, NaN, ""]

為了檢查 user 物件的 name 屬性有沒有問題,可能會寫出以下的程式碼來 Debug:

const user = {
  name: ""
};
if (!user.name) {
  console.log('哪邊出錯了QQ', user);
}

這種狀況使用 console.assert 就不需要另外加入 if 判斷式,不但寫了更少程式碼,在語意上也很清晰:「條件不符就拋錯。」:

console.assert(user.name, '哪邊出錯了QQ', user);

唯一要注意的就是第一個參數必須是 falsy 值才會出現 error,條件寫反了甚麼事情都不會發生。
 

console.count

console.count(label) 會印出這個標籤被執行了幾次,預設值是 default,可以用在快速的計數。

可以用以下的程式碼試試 console.count 的效果:

function count(arg) {
  console.count(arg);
}
count('foo');
count('bar');
count('bar');

也能用來檢查多種狀況的出現次數:

for (let i = 0; i < 5; i++) {
  const int = Math.ceil(Math.random() * 100);
  if (int < 20) console.count('太高了');
  if (int > 20) console.count('太低了');
}


 

console.countReset

count 相生,用來歸零,可用在計算單次行為的觸發的計數,例如想在 React 中計算按下按鈕後總共觸發了幾次 Render。

同步的狀況下 Event handler 內的 setState 會被 React Batch 在一起,但非同步時每個 setState 都會觸發 Render,因此以下範例在點擊按鈕後會觸發 3 次 Render。

function App() {
  const [count, setCount] = React.useState(0);
  const [count2, setCount2] = React.useState(0);
  const [asyncCount, setAsyncCount] = React.useState(0);
  const [asyncCount2, setAsyncCount2] = React.useState(0);
  const onClick = () => {
    console.countReset('render'); // 計算前先把 'render' 歸零
    setCount(count + 1); // 1
    setCount2(count2 + 1); // 1
    Promise.resolve().then(() => {
      setAsyncCount(asyncCount + 1); // 2
      setAsyncCount2(asyncCount2 + 1); // 3
    })
  }
  console.count('render');
  return (
    <div onClick={onClick}>
      <h1>Hello, please click me.</h1>
      <h2>{count}</h2>
    </div>
  );
}

或是到 Demo 頁面 React Render Counter 打開 Console 面板看看

 

console.group

為了在一大堆混亂的訊息中一眼看到自己的 log,是否曾經寫出這樣的程式碼呢?

console.log('---------');
console.log(object);
console.log('---end---');

雖然 --- 是很顯眼沒錯,但其實有更好的做法,用console.group 可以自訂 Message group 的標籤也可以多層嵌套,並用 console.groupEnd 來關閉 Group:

console.group('Start debugging');
console.log('de-');
console.group('Nested');
console.warn('deeper message');
console.groupEnd();
console.log('bug');
console.groupEnd();

另外還有 Group 的兄弟 console.groupCollapsed,只差在預設 Gourp 是閉合的需要手動展開。

 

console.table

如果在這些 Console API 中只能選一個來介紹,那肯定是 console.table,筆者自己就常常用到它。

需要印出陣列中的物件時,比起直接用 console.log 印出再慢慢展開,console.table 絕對是更好的選擇,來看看以下範例。

const rows = [
  {
    "name": "Frozen yoghurt",
    "calories": 159,
    "fat": 6,
    "carbs": 24,
    "protein": 4
  },
  {
    "name": "Ice cream sandwich",
    "calories": 237,
    "fat": 9,
    "carbs": 37,
    "protein": 4.3
  },
  {
    "name": "Eclair",
    "calories": 262,
    "fat": 16,
    "carbs": 24,
    "protein": 6
  }
];

直接執行 console.log(rows) 會發生什麼事情呢?

這絕對不會是 Debug 時想要看到的東西,需要手動展開物件才能看到內容,如果按住 OptionAlt 來一次展開全部屬性呢?

只能展開第一個物件,而且把 __proto__ 都給展開了,試試 console.table(rows) 會印出甚麼結果:

相較 console.log 直接印出物件本身,console.table 會以表格來印出物件內容,一次顯示更多資訊,另外可以用參數改變顯示的欄位以及拖拉調整欄位的寬度。

console.table(rows, ['name', 'fat']);

除了顯示上更為清楚外,console.table 還解決了另一個問題,試試在 Console 中執行以下程式碼,首先宣告一個物件 animal,以 console.log 印出後再修改物件的屬性:

const animal = {
  name: 'mimi',
  type: 'cat',
  other: {
    emoji: '?',
    sound: 'meow'
  }
};
console.log(animal);
animal.name = 'ami';

執行後會再手動展開物件出現以下結果:

可以看到展開前後的 name 屬性值是不同的,由於執行 console.log 的當下還沒修改 animal 的內容,正確顯示了執行 console.log 當下的物件值,也是一般預期想看到的值,但手動展開物件的時候值已經改變了,而使用 console.table 的話正好能避開這種困惑的情況。

不過這樣的行為其實不是個問題,執行 console.log 時右上角會有一個小圖示,Hover 上去會看到提示 Value below was evaluated just now.,說明了看到的是展開當下物件的值。

同場佳映:當物件內容較深的時候,JSON.stringify(animal, null, 2) 也是不錯的選擇,直接將物件轉為 JSON 字串全部顯示。

 

console.time

想要測量如使用者行為或是 Function 執行的時間的話,很常看到一種方式 -- 算數學:

const t0 = performance.now();
alert('Hello World!');
const t1 = performance.now();
alert('Another Hello World!');
console.log(`Spent: ${t1 - t0} ms`);
const t2 = performance.now();
console.log(`Spent: ${t2 - t0} ms`);

想要快速測試時間還寫了這堆程式碼實在有點惱人,用 console.time 來改寫一下,和 console.group 一樣可以傳入標籤參數來識別計時器:

console.time('Spent');
alert('Hello World!');
console.timeLog('Spent');
alert('Another Hello World!');
console.timeEnd('Spent');

用法非常簡單,用 time 啟動計時器後可用無限個 timeLog 來印出目前過了多久時間,最後用 timeEnd 來停止計時器,如果在 timeLogtimeEnd 中放入未啟動的標籤會噴 Warning。

 

console.trace

如果出問題的部分和其他套件有關係,尤其是一個 Function 會在多處被使用的時候,有別於 console.log 只能得知執行當下程式碼的位置,console.trace 會印出 Call stack 並直接展開,能更快速看出問題:

function a() {
  console.trace();
}
function b() {
  a();
}
function c() {
  b();
}
b()
c()

筆者曾經在使用影片播放器套件的時候,不知道是不是自己寫壞了,有時影片會突然暫停,那時就用了類似以下的程式碼來檢查,馬上就看出是套件中某個 Function 觸發了暫停。

video.addEventListener('pause', console.trace);

 

小結

今天講解的 Console API 有許多個都是筆者愛用的 Debug 方式,確實節省了不少時間呢,明天將會講解可在 Console 面板中使用,但不屬於 Console API 的內建 Debug Function。


上一篇
[Day 06] Console - Messages & Settings
下一篇
[Day 08] Console - Utilities Function
系列文
你所不知道的各種前端 Debug 技巧30

尚未有邦友留言

立即登入留言