新聞中心
Chrome DevTools 有一個覆蓋率檢測的功能,可以檢測 JS、CSS 代碼里有哪些執(zhí)行了,哪些沒執(zhí)行。并且還會在 sources 里標(biāo)記出來。

創(chuàng)新互聯(lián)建站總部坐落于成都市區(qū),致力網(wǎng)站建設(shè)服務(wù)有成都網(wǎng)站建設(shè)、網(wǎng)站設(shè)計、網(wǎng)絡(luò)營銷策劃、網(wǎng)頁設(shè)計、網(wǎng)站維護、公眾號搭建、小程序開發(fā)、軟件開發(fā)等為企業(yè)提供一整套的信息化建設(shè)解決方案。創(chuàng)造真正意義上的網(wǎng)站建設(shè),為互聯(lián)網(wǎng)品牌在互動行銷領(lǐng)域創(chuàng)造價值而不懈努力!
如下圖,綠色的部分是執(zhí)行過的,而紅色的部分是沒執(zhí)行的:
在 sources 面板里可以直接看到哪些代碼沒執(zhí)行,比如下面的紅色部分就是沒有執(zhí)行的:
這個功能還是很有用的,可以幫助我們分析哪些代碼是用不到的,可以進行延后加載或者刪掉等優(yōu)化。
在 More Tools 里開啟:
使用還是很簡單的,但它是怎么實現(xiàn)的呢?
代碼是否運行過的數(shù)據(jù)只有運行時才能得到,所以肯定是 Chrome 暴露出來,傳給 Chrome DevTools 做分析和展示的。
Chrome 和 Chrome DevTools 的通信是通過 CDP(Chrome DevTools Protocol)協(xié)議。
傳輸協(xié)議數(shù)據(jù)有多種信道,遠程調(diào)試的時候是通過 WebSocket,嵌入的時候就直接通過全局變量了。
Chrome 啟動的時候,可以通過 --remote-debugging-port 指定 ws 服務(wù)的端口:
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --remote-debugging-port=9222
我們自己實現(xiàn)一個 ws 客戶端連上它,就能拿到所有的 CDP 數(shù)據(jù)。
那我們是否能自己實現(xiàn)一下 JS、CSS 的覆蓋率檢測功能呢?
肯定是可以的。
自己實現(xiàn) ws 客戶端,傳輸 CDP 協(xié)議數(shù)據(jù)這部分可以用 google 提供的一個包 chrome-remote-interface。
const CDP = require('chrome-remote-interface');
async function test() {
let client;
try {
client = await CDP({
host: '127.0.0.1',
port: 9222
});
const { Page, DOM, Debugger, Runtime, CSS, Profiler } = client;
} catch(err) {
console.error(err);
}
}
test();連接上 9229 端口,通過各個域的 api 進行 CDP 的交互即可。
CDP 協(xié)議分為了很多個域來管理,比如 DOM、CSS、Debugger 等:
可以通過 Chrome DevTools 的 Protocol Monitor 來查看傳輸?shù)膮f(xié)議數(shù)據(jù):
數(shù)據(jù)交互分為兩類,一類是服務(wù)端推送過來的事件,另一類是向服務(wù)端請求的數(shù)據(jù)。
CDP 介紹完了,接下來我們實現(xiàn)下覆蓋率檢測的功能。
首先,我們要知道頁面下載了哪些 JS 和 CSS。
這個是通過監(jiān)聽事件拿到的, CSS.styleSheetAdded 和 Debugger.scriptParsed 這倆事件。
我們監(jiān)聽下這倆事件:
const CDP = require('chrome-remote-interface');
async function test() {
let client;
try {
client = await CDP({
host: '127.0.0.1',
port: 9222
});
const { Page, DOM, Debugger, Runtime, CSS } = client;
await Page.enable();
await Debugger.enable();
await DOM.enable();
await CSS.enable();
CSS.on('styleSheetAdded', async (event) => {
debugger;
})
Debugger.on('scriptParsed', async (event) => {
debugger;
})
await Page.navigate({url: 'http://127.0.0.1:8084'});
} catch(err) {
console.error(err);
}
}
test();因為用到 DOM、CSS、Debugger、Page 域的協(xié)議,所以需要先 enable 一下,只有 enable的功能才會啟用。
這個很正常,沒 enable 就不啟用,這樣能節(jié)省性能。
執(zhí)行這段代碼,看下拿到的事件對象:
事件對象里是這段 js 的 url 和行列號,再就是 scriptId。
然后再看下 CSS.styleSheetAdded 的事件對象:
也差不多,只不過這里是 styleSheetId。
那怎么拿到 CSS 和 JS 的內(nèi)容呢?
這就需要用到別的 api 了。
css 的內(nèi)容是用 CSS.getStyleSheetText 來拿,傳入 styeleSheetId:
const styleSheetId = event.header.styleSheetId;
const content = await CSS.getStyleSheetText({ styleSheetId });
JS 的內(nèi)容是用 Debugger.getScriptSource 來拿,傳入 scriptId:
const scriptId = event.scriptId;
const content = await Debugger.getScriptSource({ scriptId });
我們把它們按照 id 放到 Map 里:
const cssMap = new Map();
const jsMap = new Map();
CSS.on('styleSheetAdded', async (event) => {
const styleSheetId = event.header.styleSheetId;
const content = await CSS.getStyleSheetText({ styleSheetId });
cssMap.set(styleSheetId, {
meta: event.header,
content: content.text
});
})
Debugger.on('scriptParsed', async (event) => {
const scriptId = event.scriptId;
const content = await Debugger.getScriptSource({ scriptId });
jsMap.set(scriptId, {
meta: event,
content: content.scriptSource
});
})
這樣就能把頁面上所有的 js 和 css 收集起來:
對了,測試頁面的內(nèi)容是這樣的:
Document
有一個外部 css:
.aaa {
color: red;
}
div {
color: blue;
}
body {
background: pink;
}收集到了 JS 和 CSS 的數(shù)據(jù)只是第一步,要計算出覆蓋率數(shù)據(jù),還要知道哪些 JS 和 CSS 執(zhí)行了。
這個也有 api:
CSS 開啟執(zhí)行數(shù)據(jù)的收集是用 CSS.startRuleUsageTracking:
await CSS.enable();
await CSS.startRuleUsageTracking();
然后一段時間后 stop:
// 延遲一段時間再獲取數(shù)據(jù),等頁面渲染完
await new Promise(resolve => setTimeout(resolve, 3000));
const cssCoverage = await CSS.stopRuleUsageTracking();
這樣就能獲取 CSS 的執(zhí)行數(shù)據(jù):
返回的結(jié)果顯示 scriptId 為 89607.4 的 css 的 50 到 80 個字符的代碼執(zhí)行了。
我們在 cssMap 里看下這個 id 對應(yīng)的代碼:
然后取出 50 到 80 個字符的代碼:
也就是說所有 css 里只有這一段代碼是生效的:
你用 Chrome DevTools 的 Coverage 分析結(jié)果也是這樣的:
有了所有 CSS 代碼的數(shù)據(jù),有了執(zhí)行了哪些 CSS 的代碼的數(shù)據(jù),覆蓋率的計算不就很簡單了么?
我們再來看下 JS 的:
JS 使用 Profiver 的 prociseCoverage 的 api 獲取覆蓋率數(shù)據(jù):
await Profiler.enable();
await Profiler.startPreciseCoverage();
// 延遲一會再獲取數(shù)據(jù),等 js 執(zhí)行完
await new Promise(resolve => setTimeout(resolve, 3000));
const jsCoverage = await Profiler.takePreciseCoverage();
可以看到返回了兩個 script 的執(zhí)行數(shù)據(jù):
因為我們頁面上就兩個 script 嘛:
第一個 script 有 4 個 functions:
有同學(xué)說,不對呀,不是 add、minus、multiply 3 個嗎?
那個沒有名字的代表 script 的匿名代碼塊。
每個 function 都記錄了字符的范圍,還有執(zhí)行的次數(shù):
比如 add 函數(shù)執(zhí)行了 1 次:
minus 函數(shù)執(zhí)行了 0 次:
第二個 script 的匿名代碼塊執(zhí)行了 1 次:
這不就和 Chrome DevTools 的 Coverage 結(jié)果對上了么:
不管是覆蓋率數(shù)據(jù)也好,還是在 sources 里可視化展示哪些代碼沒執(zhí)行也好,都很容易實現(xiàn)。
這部分的全部代碼如下,感興趣的同學(xué)可以試試:
const CDP = require('chrome-remote-interface');
async function test() {
let client;
try {
client = await CDP({
host: '127.0.0.1',
port: 9222
});
const { Page, DOM, Debugger, Runtime, CSS, Profiler } = client;
await Page.enable();
await Debugger.enable();
await DOM.enable();
await CSS.enable();
await Profiler.enable();
const cssMap = new Map();
const jsMap = new Map();
CSS.on('styleSheetAdded', async (event) => {
const styleSheetId = event.header.styleSheetId;
const content = await CSS.getStyleSheetText({ styleSheetId });
cssMap.set(styleSheetId, {
meta: event.header,
content: content.text
});
})
Debugger.on('scriptParsed', async (event) => {
const scriptId = event.scriptId;
const content = await Debugger.getScriptSource({ scriptId });
jsMap.set(scriptId, {
meta: event,
content: content.scriptSource
});
})
await CSS.startRuleUsageTracking();
await Profiler.startPreciseCoverage();
await Page.navigate({url: 'http://127.0.0.1:8084'});
await new Promise(resolve => setTimeout(resolve, 3000));
const cssCoverage = await CSS.stopRuleUsageTracking();
const jsCoverage = await Profiler.takePreciseCoverage();
debugger;
} catch(err) {
console.error(err);
}
}
test();有的同學(xué)可能問了,Chrome DevTools 會用不就行了么,我管它怎么實現(xiàn)的干嘛?
確實,大多數(shù)業(yè)務(wù)開發(fā)同學(xué)會用 Chrome DevTools 就行了,但是如果你要實現(xiàn)一個調(diào)試工具呢?那就要深入理解它的原理了。而且理解了原理,你再去用也更加得心應(yīng)手。
更重要的是,通過 api 的方式,你是能拿到運行時的數(shù)據(jù)的,可以自己做一些計算和處理,然后把數(shù)據(jù)存下來之類的。
不知道大家有沒有聽說過 lighthouse,就是分析頁面性能、可訪問性等等數(shù)據(jù),然后給出一個得分和優(yōu)化建議的工具:
它其實是有獨立的 cli 的:
在 cli 里怎么收集網(wǎng)頁的數(shù)據(jù),然后做分析呢?
其實它就是通過 Chrome 運行網(wǎng)頁,然后 CDP 的方式收集各種數(shù)據(jù),然后做分析和展示的。
如果某一天,你也要做一個網(wǎng)頁分析工具,是不是也可以通過 CDP 的方式來獲取一些網(wǎng)頁運行數(shù)據(jù)做分析呢?
所有 Chrome DevTools 的數(shù)據(jù),你通過 CDP 都是能拿到的,能做的事情有很多。
總結(jié)
Chrome DevTools 有 Coverage 面板,可以分析 JS 和 CSS 代碼執(zhí)行的覆蓋率,分析出哪些代碼沒執(zhí)行,然后做后續(xù)優(yōu)化。
這是 Chrome 通過 CDP 暴露給 Chrome DevTools 的,而 CDP 的數(shù)據(jù)我們也能自己實現(xiàn) ws 客戶端來拿到,那自然也可以自己實現(xiàn)覆蓋率的計算。
我們通過 chrome-remote-interface 的不同域的 api 來進行了 CSS 和 JS 的代碼的收集,代碼執(zhí)行數(shù)據(jù)的收集,有了這些數(shù)據(jù)就能輕松算出覆蓋率。
lighthouse 的 cli 就是通過這種方式來收集 Chrome 運行時數(shù)據(jù),做分析和展示的。如果我們想做一個調(diào)試工具,或者網(wǎng)頁分析工具,也可以用類似的思路。
Chrome DevTools 能做的所有事情,我們都能自己實現(xiàn),因為 CDP 數(shù)據(jù)是一摸一樣的。
你還對啥 Chrome DevTools 的功能感興趣呢?不如我們自己來實現(xiàn)一下?
當(dāng)前名稱:自己實現(xiàn)ChromeDevTools的Coverage功能
網(wǎng)頁鏈接:http://www.dlmjj.cn/article/ccieche.html


咨詢
建站咨詢
