挖掘Chrome Console的小祕密

SP-Lyu發表於2018-10-09

控制檯應該是大多數前端開發人員日常開發除錯離不開的神器。然而控制檯仍有很多不為人知的屬性及方法,能讓你更爽地使用,當然也包括了一些隱藏的深坑(console.log物件列印bug #feature)...
* 本文探討的是chrome開發工具中的控制檯,其他瀏覽器也許存在不同的表現,此文不涉獵
* 物件列印問題在本文的最末尾點選跳轉

Try it first!

開始前,讓我們來造一個馬里奧!
開啟開發者工具的控制檯,將下述程式碼複製貼上,然後猛敲回車!

!(navigator.userAgent.toLowerCase().indexOf('chrome') > -1) ? null : (function() {
    var args = [],  eightBitHack = [], coordinates = ["41n8r2", "42t3wu", "449u8a", "4h4014", "4h2c4y", "4g6ia1", "4286dm", "447r6w", "4fudcv", "61z2xp", "70rmyd", "71sfq1", "6zgplp", "42spfv", "4frvnp", "61wzpd"];
    for (var row = coordinates.length; row--;) {
        var decompressedRow = parseInt(coordinates[row], 36).toString(5).split('');
        coordinates[row] = decompressedRow.splice(1, decompressedRow.length-1);
        for (var cell = coordinates[row].length; cell--;) {
            var dot = parseInt(coordinates[row][cell]);
            var color = dot === 4 ? '#ecd585' : dot === 3 ? '#e1c25b' : dot === 2 ? '#805936' : dot ? '#ec2733' : '#fff';
            args.unshift("border: 8px solid color;".replace('color', color));
            eightBitHack.unshift("%c");
        }
        eightBitHack.unshift("\n");
    }
    eightBitHack.push("%c\n\n\n", "\nIt's me, Mario!", "\nmade by %chttps://twitter.com/aristretto");
    args.push("font-weight: bold;", "font-weight: bold; color: teal;");
    args.unshift(eightBitHack.join(''));
    console.log.apply(console, args);
})();
複製程式碼

可以看到,一個充滿色彩的馬里奧出現在了控制檯

Mario

主要是使用了以下命令實現: console.log('%c','/*css*/');

*當然這裡面還有到了其他的trick處理方式,詳情請看作者連結

控制檯進階玩法

從上述的例子可以看出,控制檯還有很多我們不知道的進階玩法,如輸出更好的格式,利用一些trick,使得我們的除錯更具效率。

Console API

在控制檯中輸入console.可以看出,console中有很多方法可以呼叫,網上已有很多資源說明,此次僅提及幾個比較有用的API

console.log(object[,object,...])

console.log除了常見的將需要輸出的結果直接傳入第一個引數中,還有以下用法:

  • 使用逗號分隔,傳入多個引數輸出
    輸出時會將每個逗號自動替換成空格
console.log('Hello','world!\t','Current Time:',Date.now());
// Hello world!	Current Time: 1530694211342
複製程式碼
  • 使用說明符輸出,類似於C++中的print函式
    除了一般的%s(字串)、%i``%d(均為整型)、%f外,還支援%c(樣式)、%o(DOM元素)、%O(JavaScript物件)輸出
console.log('Hello world!\t%s: %i','Current Time',Date.now());
console.log("normal text,%c large blue text,%c white text with black background ", "color: blue; font-size: x-large","color:white;background:black;");
console.log('%o\n%O',document,{a:1,b:2,c:3});
複製程式碼

上述程式碼輸出結果見下圖:

console.log with format

console.count

寫入在同一行使用相同標籤呼叫count()的次數,可用於某些setInterval或者事件重複觸發的除錯。

const fn = function(name){
    console.count(name);
};
fn('Bob');      // Bob: 1
fn('John');     // John: 1
fn('Bob');      // Bob: 2
fn('Bob');      // Bob: 3
複製程式碼

console.error console.trace

輸出一條訊息,幷包含了呼叫該方法的地方的堆疊資訊。區別是error會將訊息設定成錯誤的樣式。

(()=>{
    const fn1 = (fn)=>{
        fn();
    };
    const fn2 = ()=>{
        console.trace('Target Not Found');
        console.error('Target Not Found');
    };
    fn1(fn2);
})();
複製程式碼

上述程式碼輸出結果見下圖:

console.error

console.time timeEnd

啟動一個具有關聯標籤的新計時器。使用相同標籤呼叫console.timeEnd()時,定時器將停止,經過的時間將顯示在控制檯中。計時器值精確到亞毫秒。傳遞到time()timeEnd()的字串必須匹配,否則計時器不會結束。
可用於分析某段程式碼的時間消耗。

console.time('test');
for(let i = 0; i < 100000000; i++){}
console.timeEnd('test');
// 輸出:
// test: 122.71923828125ms
複製程式碼

Others

此外,還有很多好用的Console API,如console.tableconsole.groupconsole.assert等。可以在Chrome的Console API文件中找到他們的使用方法.

Command Line API

嘗試一下,在一個未引入jQueryzepto的頁面的控制檯中,直接輸入$$$會出現什麼?

// 直接開啟控制檯輸入
console.log($,$$);
// 輸出:
// ƒ $(selector, [startNode]) { [Command Line API] } ƒ $$(selector, [startNode]) { [Command Line API] }
複製程式碼

可以看到,輸出的函式中,包含了[Command Line API]Command Line API是由控制檯提供的一系列便捷函式集合,大概的功能有:選擇和檢查 DOM元素,以可讀格式顯示資料,停止和啟動分析器,以及監控 DOM 事件。
* 注1:此類API僅通過控制檯本身獲取,在JS程式碼中帶上此類程式碼會報錯。
* 注2:若全域性已覆蓋了相同名稱的方法,則此類方法將被覆蓋。

$、$$

$(selector)等同於document.querySelector,同樣的,$(selector)等同於document.querySelectorAllCommand Line API只是提供了較快捷的方式便於開發者進行除錯。

$0-$4

$0$1$2$3$4命令用作在 Elements 皮膚中檢查的最後五個DOM元素或在 Profiles皮膚中選擇的最後五個JavaScript堆物件的歷史參考。$0返回最近一次選擇的元素或JavaScript物件,$1返回僅在最近一次之前選擇的元素或物件,依此類推。
以下結果是在測試頁面上依次點選html標籤、head標籤、meta標籤的結果:

\$0 - \$4

others

此外,還有很多好用的Command Line API,如copydebugmonitorprofile等。可以在Chrome的Command Line API文件中找到他們的使用方法.

控制檯的坑

試想一下以下程式碼在控制檯中輸出的結果:

const fn = function(length){
    const o = {
        arr: [],
        key1: 'test',
        key2: 'test',
        key3: 'test',
        key4: 'test',
        key5: 'test',
        index: 0
    };
    console.log(JSON.stringify(o));
    console.log(o);
    console.log('Handling data');
    for(let i = 0; i < length; i++){
        o.arr.push(i);
    }
    o.index = length;
    console.log(JSON.stringify(o));
    console.log(o);
};
fn(5);
複製程式碼

不難看出,控制檯中輸出的結果應該如下圖:

console with complex object

此時,我們展開一下第二行與第五行,會發現一個很奇怪的現象:

object expand
展開第二行發現,arr裡的長度是5,物件的index值居然是5!

延時計算問題

定位上述的問題,只需要將滑鼠移至行尾的藍色info標記上,控制檯會提示以下內容:
Value below was evaluated just now. #所以這不是bug是feature
這句話意味著,展開當前的object的時候,控制檯才會去計算出這個物件的key-value,再反饋到控制檯中顯示。
使用Console列印的時候,若控制檯發現當前需要列印的內容是一個物件,會將其儲存下來,先輸出一個簡要的快照(Snapshot),待開發者需要檢視其中詳細內容時,再點選展開,返回記憶體中的值。
這個是控制檯的一個已知的坑點,有可能設計該功能是為了避免控制檯對大物件深複製輸出,導致除錯過慢,也有可能是為了方便檢視原型鏈上的屬性,但這無疑是開發者除錯程式碼時需要避開的問題。
要避免這種除錯問題,建議使用JSON.stringify()進行輸出除錯,而不是直接列印當前物件。

記憶體洩漏問題

上面提到,使用Console列印物件時,會將這個物件的引用儲存下來。由於開發者工具在瀏覽器中預設開啟,且預設了“開發者之後需要檢視該物件”的行為,就會導致在Console中引入的物件是不會進入GC(垃圾回收)邏輯中的,這就引發了記憶體洩漏問題。 要避免記憶體洩漏問題,需要將開發環境與線上環境進行分離,線上環境中避免產生控制檯列印的語句,亦可以在專案打包時,將ESLint中的no-console的開關開啟。

參考及擴充

medium.com/@aristretto…
developers.google.com/web/tools/c…
stackoverflow.com/questions/1…

相關文章