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

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


咨詢(xún)
建站咨詢(xún)
