不是 Console API 的 Console 工具#

除了 Console API(console.logconsole.table 等可以寫在程式碼裡的 method)之外,DevTools 還在 Console panel 內提供了一組 Debug 專用的 utility function。這些是 Console Utilities API,每一個都身懷絕技。

這些 utility 只在 Console panel 內存在,把它們寫進實際的程式碼會直接拋 Uncaught ReferenceError: xxx is not defined。原作者第一次學到這些功能太興奮、寫進程式碼裡才發現這件事。

$_#

$_ 儲存了 Console 上一次執行的結果。在 Console 試 JavaScript 行為時通常都是逐步驗證,這時 $_ 非常順手。

[1, 2, 3, 4].map((x) => x * 2);
// $_ 現在是 [2, 4, 6, 8]
$_.reduce((a, b) => a + b);
// 20

當需要串接很多不能用 method chaining 的 function 時,可以利用 $_ 避免游標來回修改:每次只動最後一行,不會破壞前面打好的程式碼。

未來 JavaScript 也許會有 Pipeline operator(TC39 提案)做到任意 function chaining,提升可讀性、不需要修改內建 prototype。

let a;
a = 1 |> ((n) => add(n, 5)) |> double;
console.log(a); // 12

$, $$#

  • $(selector[, element]):等於 document.querySelector
  • $$(selector[, element]):等於 document.querySelectorAll

這兩個語法借自 jQuery,是 Console 內部寫死的 alias。

第二個參數可以指定起始元素,搭配 $0 就能「先 Inspect 一個元素,再從它底下搜尋」:

$(".btn", $0);

原作者常用 $$ 在 Console 快速測一些行為,例如印出個人 GitHub 頁面的所有 repository 名稱:

$$("h3 a").map((a) => a.textContent.trim());

如果頁面已經引入 jQuery,$ 會是 jQuery 本身,而不是 DevTools 的 alias。

$x#

$x(xpath[, element]) 用 XPath 查詢元素,回傳陣列。當 CSS 選取器(Selector)無法表達某些查詢條件(例如「找到內文是某段文字的元素」)時很有用。

$x("//a[contains(text(), 'Login')]");

debug / undebug#

debug(function) 把指定 function 標記成「執行時自動觸發 debugger」:

function a() {
  console.log(1);
}

debug(a);
// undebug(a);  // 取消

效果等同於:

function a() {
  console.log(1);
}
a = (function () {
  const origin = a;
  return function () {
    debugger;
    origin();
  };
})();

關於斷點(Breakpoint)的詳細用法,會在 Sources panel 的章節展開。

monitor / unmonitor#

monitor(function)debug 用法類似,差別在:被 monitor 的 function 執行時會印出 function 名稱與實際傳入的參數,但不會中斷執行。

function greet(name) {
  return `Hello, ${name}`;
}
monitor(greet);
greet("Andrew");
// function greet called with arguments: Andrew

unmonitor(function) 停止監控。

monitor 不能用在 arrow function。要監控 arrow function,只能手動覆寫。

monitorEvents#

monitorEvents(element[, eventType]) 監聽元素的事件並印出來。可以聽單一事件,也能聽一整類事件:

monitorEvents(window, "click");
monitorEvents(window, "touch"); // 一次聽 touchstart, touchmove, touchend, touchcancel

第二個例子等效於:

window.addEventListener("click", console.log);
window.addEventListener("touchstart", console.log);
window.addEventListener("touchmove", console.log);
window.addEventListener("touchend", console.log);
window.addEventListener("touchcancel", console.log);

unmonitorEvents(element[, eventType]) 停止。

getEventListeners#

getEventListeners(element) 印出已註冊在該元素上的事件監聽器。

延續上面的例子,先 monitorEvents(element)getEventListeners(element),會看到所有事件都被註冊了一輪。

展開後可以看到每個 listener 的屬性:

  • listener:實際被觸發的 function
  • once:是否為一次性監聽
  • passive:是否不能呼叫 event.preventDefault(),常用來提升 scroll 等高頻事件的效能
  • type:監聽的事件類型
  • useCapture:是否在 capture 階段攔截事件

這些屬性都是 addEventListener 的選項。

const options = {
  capture: true,
  passive: true,
  once: false,
};
window.addEventListener("click", console.log, options);
// window.removeEventListener('click', console.log, options);

移除 listener 時必須提供「相同的」options,否則 removeEventListener 不會生效。

queryObjects#

queryObjects(constructor) 官方說明是「列出由該 constructor 產生的所有 instance」,但更精準的說法是:印出所有「prototype 鍊上包含該 prototype」的物件。

class A {}
const a = new A();
const b = Object.create(a);
queryObjects(A);
// 會印出 a 與 b

queryObjects 不是 return 陣列、而是延遲列印結果,要把結果存起來操作可以用「右鍵 ➡️ Store as global variable」,會自動存到 temp1 等變數。

copy#

copy(object) 把 DOM 元素或物件複製到剪貼簿。

實用情境:

  • 把物件轉成 JSON 貼到對話框討論 API spec
  • 在 Console 快速建立、修改假資料後直接複製出去

複製物件時 DevTools 會自動加好縮排,不需要再手動 prettier 一次。

keys, values#

  • keys(object)Object.keys(object)
  • values(object)Object.values(object)

只回傳物件「自身」的可枚舉屬性。為什麼要強調自身?因為 for...in 會把 prototype 鍊上的所有可枚舉屬性都跑一遍:

const object = Object.create({ foo: 1 });
object.bar = 2;
for (let key in object) {
  console.log(key);
}
// bar
// foo

要過濾「只看自身屬性」,正確寫法是:

for (let key in object) {
  if (Object.prototype.hasOwnProperty.call(object, key)) {
    console.log(key);
  }
}
// bar

為什麼用 Object.prototype.hasOwnProperty.call(object, key) 而不是 object.hasOwnProperty(key)?看下面例子:

const object1 = {
  hasOwnProperty: function () {
    return false;
  },
};
const object2 = Object.create(null);

object1.key = "key";
object2.key = "key";

object1.hasOwnProperty("key"); // false(被覆寫)
object2.hasOwnProperty("key"); // TypeError(沒有繼承 Object.prototype)

object.hasOwnProperty 可能被覆寫、或根本不存在。

clear#

clear() 清空 Console panel。雖然左上角的 ? 圖示也能清,但用 clear() 像在 terminal 輸入 clear 一樣順手。

Preserve log 開啟的情況下,clear() 不會清空 Console。這是設計上的權衡:保留 log 的優先度高於 clear。

銜接#

Console Utilities API 走完了。下一章是 Console 系列的最後一章,講「在 Console 內執行 JavaScript」的小撇步:換行縮排、Default Async、Context 切換、Live Expression。

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10241598