SDK 體積與性能優(yōu)化實(shí)踐
精選背景
字節(jié)各類業(yè)務(wù)擁有眾多用戶群,作為字節(jié)前端性能監(jiān)控 SDK,自身若存在性能問(wèn)題,則會(huì)影響到數(shù)以億計(jì)的真實(shí)用戶的體驗(yàn),所以此類 SDK 自身的性能在設(shè)計(jì)之初,就必須達(dá)到一個(gè)非常極致的水準(zhǔn)。
與此同時(shí),隨著業(yè)務(wù)不斷迭代,功能變得越來(lái)越多,對(duì)監(jiān)控的需求也會(huì)變得越來(lái)越多。例如,今天 A 業(yè)務(wù)更新了架構(gòu),想要自定義性能指標(biāo)的獲取規(guī)則,明天 B 業(yè)務(wù)接入了微前端框架,需要監(jiān)控子應(yīng)用的性能。在解決這些業(yè)務(wù)需求的同時(shí),我們會(huì)不斷加入額外的判斷邏輯、配置項(xiàng)。同時(shí)由于用戶的電腦性能、瀏覽器環(huán)境的不同,我們又要解決各種兼容性問(wèn)題,加入 polyfill 等代碼,不可避免地造成 SDK 體積膨脹,性能劣化。那么我們是如何在需求和功能不斷迭代的情況下,持續(xù)追蹤和優(yōu)化 SDK 的體積和性能的呢?
SDK 體積優(yōu)化
通常而言,體積的優(yōu)化是最容易拿到收益的一項(xiàng)。
由于監(jiān)控 SDK 通常作為第一個(gè)腳本被加載到頁(yè)面中,體積的膨脹不僅會(huì)增加用戶的下載時(shí)間,還會(huì)增加瀏覽器解析腳本的時(shí)間。對(duì)于體積優(yōu)化,我們可以從宏觀和微觀兩個(gè)角度去實(shí)現(xiàn)。
微觀上,我們會(huì)去盡可能去精簡(jiǎn)所有的表達(dá),剝離冗余重復(fù)代碼,同時(shí)盡可能減少以下寫(xiě)法的出現(xiàn):
1.過(guò)多的 class 和過(guò)長(zhǎng)的屬性方法名
Class 的定義會(huì)被轉(zhuǎn)換成 function 聲明 + prototype 賦值,以及常用代碼壓縮工具無(wú)法對(duì) object 屬性名壓縮,過(guò)多的面向?qū)ο髮?xiě)法會(huì)讓編譯后的 js 代碼體積膨脹得非常快。例如下列代碼:
經(jīng)過(guò) ts 轉(zhuǎn)換后會(huì)變成:
壓縮后代碼為:
可以看到以上長(zhǎng)命名都無(wú)法被壓縮。
如果使用函數(shù)式編程來(lái)代替面向?qū)ο缶幊蹋軌蚝芎玫谋苊獯a無(wú)法被壓縮的情況:
經(jīng)過(guò)壓縮后變成:
相較于 class 的版本,壓縮后的代碼減小了50%以上。
2、內(nèi)部函數(shù)傳參使用數(shù)組代替對(duì)象
原理同上,對(duì)象中的字段名通常不會(huì)被代碼壓縮工具壓縮。同時(shí)合理使用 TS named tuple 類型可以保證代碼可維護(hù)性。
改為:
3、在不需要判斷 nullable 時(shí),盡可能避免?. ?? ??= 等操作符的出現(xiàn)。同理,盡可能避免一些例如 spread 操作符、generator 等新語(yǔ)法,這些語(yǔ)法在編譯成 es5 后通常會(huì)引入額外的 polyfill。
TS 會(huì)將這些操作符轉(zhuǎn)換成非常長(zhǎng)的代碼,例如 a?.b會(huì)被轉(zhuǎn)換成:
過(guò)多的 nullish 操作符也是代碼體積增加的一個(gè)原因。
當(dāng)然,以上只列舉了部分體積優(yōu)化措施,還有更多優(yōu)化方法要結(jié)合具體代碼而議。對(duì)于我們的前端監(jiān)控 SDK,為了性能和體積是可以犧牲一些開(kāi)發(fā)體驗(yàn)的,并且由于使用 TS 類型系統(tǒng),并不會(huì)對(duì)代碼維護(hù)增加很多負(fù)擔(dān)。
從宏觀上,我們應(yīng)該思考如何減少 SDK 所依賴的模塊,減少產(chǎn)物包含的內(nèi)容,增加產(chǎn)物的“信噪比”,有以下幾個(gè)方式:
1.拆分文件
我們可以分離出 SDK 中不是必須提前執(zhí)行的邏輯,拆分成異步加載的文件,僅將必須提前執(zhí)行的邏輯加入初始腳本。同時(shí)將不同功能拆分成不同文件,業(yè)務(wù)按需加載,這樣可以最大程度減少對(duì)首屏加載時(shí)間的影響。
2.盡可能避免 polyfill 的使用
polyfill 會(huì)顯著增加產(chǎn)物體積,我們盡可能不使用存在兼容性的方法。甚至在不需要兼容低端瀏覽器環(huán)境時(shí),我們可以不使用 polyfill。
3.減少重復(fù)的常量字符串的出現(xiàn)次數(shù)
對(duì)于多次重復(fù)出現(xiàn)的常量字符串,提取成公共變量。例如
我們可以將 addEventListener?和 load 提取公共變量:
此段代碼壓縮后會(huì)變成:
我們還可以使用 TSTransformer 或者 babel plugin 來(lái)幫我們自動(dòng)地完成上述過(guò)程。
值得注意的是,這個(gè)方法在 web 端并不能取得很好的收益,因?yàn)闉g覽器在傳輸數(shù)據(jù)時(shí)會(huì)做 gzip 壓縮,已經(jīng)將重復(fù)信息用最高效的算法壓縮了,我們做的并不會(huì)比 gzip 更好。但是在需要嵌入移動(dòng)端 app 的監(jiān)控 SDK 來(lái)說(shuō),這一做法能減少約 10 ~ 15% 產(chǎn)物體積。
除了體積優(yōu)化以外,隨著需求不斷增加,功能不斷完善,不可避免的會(huì)影響到 SDK 的性能。接下來(lái),我們介紹如何測(cè)量并優(yōu)化 SDK 的性能。
使用工具進(jìn)行性能衡量
通常來(lái)說(shuō),監(jiān)控類 SDK 最有可能影響性能的地方為:
- 監(jiān)控初始化時(shí)執(zhí)行各類監(jiān)聽(tīng)的過(guò)程。
- 監(jiān)控事件上報(bào)請(qǐng)求對(duì)業(yè)務(wù)的影響。
- SDK 維護(hù)數(shù)據(jù)緩存時(shí)的內(nèi)存使用情況。
接下來(lái),我們著重從以上幾個(gè)維度來(lái)衡量并優(yōu)化 SDK 的性能。
性能衡量過(guò)程
使用 Benchmark 性能衡量工具的目的便是為了知道 SDK 運(yùn)行過(guò)程中每一個(gè)函數(shù)執(zhí)行的耗時(shí),給業(yè)務(wù)帶來(lái)多大的影響,是否會(huì)引起 longtask。由于我們的監(jiān)控 SDK 包含了性能、請(qǐng)求、資源等各類前端監(jiān)控能力,這些功能的實(shí)現(xiàn)依賴對(duì)頁(yè)面各類事件的監(jiān)聽(tīng)、性能指標(biāo)的獲取、請(qǐng)求對(duì)象的包裝。除此之外,SDK還提供給用戶(開(kāi)發(fā)者)調(diào)用的方法,例如配置頁(yè)面信息、自定義埋點(diǎn)、更改監(jiān)控行為等能力。根據(jù) SDK 以上行為和能力,我們將測(cè)試分為兩個(gè)模塊:
- 接入 SDK 后自動(dòng)運(yùn)行的各類監(jiān)控,這些行為大部分會(huì)在頁(yè)面加載之初執(zhí)行,若此部分性能劣化,會(huì)嚴(yán)重影響到所有前端業(yè)務(wù)用戶的首屏加載。
- 用戶端(開(kāi)發(fā)者)調(diào)用的方法,我們會(huì)將此類方法包裝成 client 對(duì)象以 npm 包的形式給開(kāi)發(fā)者調(diào)用,這部分方法的執(zhí)行由用戶控制,可能存在頻繁調(diào)用的情況,因此也應(yīng)避免耗時(shí)過(guò)長(zhǎng)的調(diào)用出現(xiàn)。
在過(guò)往文章前端監(jiān)控系列1| 字節(jié)的前端監(jiān)控 SDK 是怎樣設(shè)計(jì)的中我們講到,我們的 SDK 在設(shè)計(jì)時(shí)已經(jīng)做到的盡可能的解耦,各個(gè)模塊各司其職,這一特點(diǎn)非常便于我們針對(duì)各個(gè)模塊方法進(jìn)行單獨(dú)的性能衡量。
下面我們以使用 benny (https://github.com/caderek/benny) 這一開(kāi)源工具為例,展示一段方便理解 benchmark 過(guò)程的偽代碼,僅作參考:
benny 是一個(gè)非常簡(jiǎn)單易用的 benchmark 工具,通過(guò) suite? 方法創(chuàng)建測(cè)試用例組合,通過(guò)add?方法添加需要測(cè)試的函數(shù),cycle?方法用于多次循環(huán)執(zhí)行測(cè)試用例,complete用于添加測(cè)試完成之后的回調(diào)函數(shù)。更多詳細(xì)的使用說(shuō)明可以查閱官方文檔。
通常這類 benchmark 工具都是在 Node 上執(zhí)行的,但是我們的 SDK 是個(gè)前端監(jiān)控 SDK,依賴了非常多的瀏覽器環(huán)境對(duì)象,我們幾乎不可能在 Node 環(huán)境去創(chuàng)造或模擬這些對(duì)象,我們有沒(méi)有辦法在瀏覽器里去運(yùn)行這段腳本,做性能自動(dòng)化測(cè)試呢?
利用 Puppeteer 在瀏覽器環(huán)境中執(zhí)行 Benchmark
由于我們的前端監(jiān)控依賴瀏覽器環(huán)境,我們可以將上述 benchmark 測(cè)試代碼打包成 commonjs 之后放入 headless chrome 瀏覽器中執(zhí)行,并通過(guò) puppeteer 收集執(zhí)行結(jié)果。
Puppeteer 是一個(gè) Node 模塊,提供了通過(guò) Devtool Protocol 控制 Chrome 或者 Chromium 的能力。Puppeteer 默認(rèn)運(yùn)行 Chrome 的無(wú)頭版本,也可以通過(guò)設(shè)置運(yùn)行 Chrome 用戶界面版。
下面是一段方便理解操作 puppeteer 過(guò)程的偽代碼,僅作參考,實(shí)際情況較為復(fù)雜,需要等待未完成的異步請(qǐng)求等:
通過(guò)運(yùn)行以上腳本,我們便可以在無(wú)頭瀏覽器中運(yùn)行我們的性能測(cè)試腳本,在測(cè)試腳本產(chǎn)出結(jié)果后添加調(diào)用 pushResult 方法來(lái)收集測(cè)試結(jié)果。
在實(shí)際的 benchmark 測(cè)試中,我們發(fā)現(xiàn)開(kāi)啟性能監(jiān)聽(tīng)(即運(yùn)行各個(gè)性能監(jiān)控的 PerformanceObserver.observe 方法)最大耗時(shí)達(dá)到了21ms,雖然看上去并不久,但若和其他監(jiān)聽(tīng)同時(shí)執(zhí)行,加上引入業(yè)務(wù)代碼的復(fù)雜性和移動(dòng)端更弱的 CPU 性能,極有可能成為給業(yè)務(wù)帶來(lái) longtask 的罪魁禍?zhǔn)?。性能監(jiān)控性能成為了瓶頸。
接下來(lái),我們將性能監(jiān)聽(tīng)一個(gè)個(gè)拆分,用同樣的方式單獨(dú)測(cè)試每一個(gè)性能監(jiān)聽(tīng)的耗時(shí)。在實(shí)際的 benchmark 結(jié)果中,我們發(fā)現(xiàn) fp、fcp、lcp、cls 監(jiān)控耗時(shí)最大,加在一起超過(guò)了10ms,占了一半以上,是我們之后需要重點(diǎn)優(yōu)化的地方。
除此之外利用 puppeteer 的能力,我們不僅可以得到 benchmark 的結(jié)果,還可以獲取到整個(gè) benchmark 過(guò)程的 profile 數(shù)據(jù),利用 speedscope (https://github.com/jlfwong/speedscope/blob/main/README-zh_CN.md) 繪制出函數(shù)執(zhí)行過(guò)程中的火焰圖:
繪制火焰圖的具體實(shí)現(xiàn)不在本文討論范圍內(nèi),感興趣的同學(xué)可以參考 speedscope 官方文檔
此處顯示的時(shí)間為該用例執(zhí)行總耗時(shí)(單次耗時(shí)*次數(shù))
如何衡量異步任務(wù)性能?
Benny 的 api 是支持異步測(cè)試用例的,測(cè)量的是每個(gè)異步函數(shù)從開(kāi)始執(zhí)行到 resolve 的時(shí)間。但通常這并不是我們想要的衡量的數(shù)據(jù),因?yàn)楫惒饺蝿?wù)的執(zhí)行過(guò)程中并不是一直占據(jù)著主線程。對(duì)于一些異步的定時(shí)任務(wù)(例如 SDK 的崩潰檢測(cè)、卡頓檢測(cè)、白屏檢測(cè)),將他們拆解為一系列可測(cè)的同步任務(wù)能更直觀的展示各個(gè)階段的性能耗時(shí)。
例如我們 SDK 的前端白屏檢測(cè),由一個(gè) mutationObserver 和觸發(fā)白屏檢測(cè)的函數(shù)組成。我們可以單獨(dú)對(duì) mutationObserver 的回調(diào)和觸發(fā)函數(shù)做性能衡量。
這兩個(gè)方法已沒(méi)有很好的優(yōu)化方式了。但是根據(jù) benchmark 結(jié)果并結(jié)合源碼可以發(fā)現(xiàn),性能監(jiān)控所有指標(biāo)項(xiàng)的開(kāi)啟均為同步執(zhí)行,每一項(xiàng)指標(biāo)都會(huì)對(duì)頁(yè)面做事件監(jiān)聽(tīng)或者 PerformanceObserver 監(jiān)聽(tīng),且這些原生監(jiān)聽(tīng)耗時(shí)都在毫秒級(jí)。于是我們對(duì)性能做了如下優(yōu)化:
- 性能監(jiān)控邏輯分片運(yùn)行,將各項(xiàng)性能指標(biāo)的監(jiān)聽(tīng)同步拆為異步,用 requestIdleCallback (https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback) 做調(diào)度并區(qū)分優(yōu)先級(jí)。
- 多個(gè)性能指標(biāo)監(jiān)聽(tīng)同一事件的公用監(jiān)聽(tīng)器,例如 CLS 和 LCP 都需要監(jiān)聽(tīng) onBFCacheRestore,讓他們只做一次 addEventListener。
- 可以延遲執(zhí)行的方法延遲執(zhí)行,例如在高版本的 Chrome 中 PerformanceObserver 是有 buffer (https://www.w3.org/TR/performance-timeline-2/#dom-performanceobserverinit-buffered) 的,可以直接獲取到調(diào)用之前的性能指標(biāo),這些方法調(diào)用就可以等待頁(yè)面完全加載完成之后執(zhí)行,從而盡可能減少對(duì)業(yè)務(wù)頁(yè)面首屏影響。
通過(guò) Perfsee 的 Lab 結(jié)果分析性能問(wèn)題
以上的 benchmark 流程得到的結(jié)果畢竟是一種理想化、單純的方法調(diào)用的性能情況,然而在實(shí)際瀏覽器環(huán)境中我們前端監(jiān)控 SDK 對(duì)性能影響有多大呢,對(duì)于這一類頁(yè)面初始化即加載的 SDK 可以通過(guò) Perfsee (https://perfsee.com/) 的 Lab 功能進(jìn)行性能衡量。
Perfsee 是一個(gè)針對(duì)前端 web 應(yīng)用在整個(gè)研發(fā)流程中的性能分析平臺(tái)。提供性能分析報(bào)告、產(chǎn)物分析報(bào)告、源碼分析、競(jìng)品分析等模塊,定位與梳理性能問(wèn)題,提供專業(yè)的優(yōu)化方案來(lái)漸進(jìn)地優(yōu)化產(chǎn)品性能。
Lab 模塊性能分析的依據(jù)是,使用 headless 瀏覽器運(yùn)行用戶指定的頁(yè)面,通過(guò)運(yùn)行時(shí)數(shù)據(jù)的收集,分析并產(chǎn)出關(guān)鍵性能指標(biāo)分?jǐn)?shù)、網(wǎng)絡(luò)請(qǐng)求信息、主線程 JS/渲染/Longtask 信息供業(yè)務(wù)方參考優(yōu)化。具體使用說(shuō)明請(qǐng)查看 perfsee.com (https://perfsee.com/docs/cn/lab/get-started)
注意,本文所展示 Perfsee 功能示例為早期版本,并不與開(kāi)源版本功能和界面完全一致。
準(zhǔn)備基準(zhǔn)頁(yè)面作為對(duì)照組
我們的目的是衡量 SDK 對(duì)業(yè)務(wù)性能造成的影響,便需要找到一個(gè)基準(zhǔn)頁(yè)面作為對(duì)比。此處以 React Server Component Demo (https://github.com/reactjs/server-components-demo) 為例作為基準(zhǔn)頁(yè)面。該應(yīng)用有以下幾個(gè)特點(diǎn):
- 容易搭建,一個(gè)命令就能跑起來(lái)。
- 自身邏輯簡(jiǎn)單,性能好,SDK 所造成的影響容易被放大觀察。
- SPA 應(yīng)用,含有異步加載的邏輯,更容易探測(cè)到監(jiān)控 SDK 對(duì)頁(yè)面 FCP、LCP 等指標(biāo)影響。
- 無(wú)外部網(wǎng)絡(luò)請(qǐng)求,頁(yè)面結(jié)果穩(wěn)定不易波動(dòng)。
我們修改一下應(yīng)用的邏輯,能夠通過(guò) url 參數(shù)注入監(jiān)控 sdk 腳本,把它部署在服務(wù)器上。接著,我們?cè)?perfsee 平臺(tái)上配置好基準(zhǔn)頁(yè)面和注入 SDK 的頁(yè)面這兩個(gè) page,并觸發(fā)一次性能掃描。
查看 Lab 性能報(bào)告
我們將沒(méi)有注入 SDK 的頁(yè)面作為空白組 (empty),注入了 SDK 的頁(yè)面作為實(shí)驗(yàn)組 (with-sdk)。
首先我們需要配置好空白組和實(shí)驗(yàn)組的 pages 以及 profile,觸發(fā)一次 snapshot 之后,我們得到了多份報(bào)告,我們可以點(diǎn)擊 compare 將空白組和實(shí)驗(yàn)組的數(shù)據(jù)進(jìn)行比對(duì)。
在實(shí)際的 lab 性能掃描結(jié)果中,我們可以看到兩個(gè)頁(yè)面所有性能指標(biāo)的對(duì)比。我們發(fā)現(xiàn) sdk 的注入在 mobile profile(4倍降頻) 下還是給業(yè)務(wù)帶來(lái)了 fcp 70ms、lcp 90ms、load 200ms 的劣化。
同時(shí)我們還可以觀察到注入了 sdk 之后,fmp 和 lcp 之前的請(qǐng)求僅多了 1 個(gè),這是符合預(yù)期的。不過(guò)這仍是我們保持觀察的指標(biāo)之一,因?yàn)樵谝恍┲械投说沫h(huán)境中,頁(yè)面加載完成之前每發(fā)出一個(gè)請(qǐng)求就可能讓業(yè)務(wù)更高優(yōu)先級(jí)的請(qǐng)求被延后,從而引起頁(yè)面性能指標(biāo)的下降。
切換到 Breakdown Tab,我們還可以看到頁(yè)面首屏?xí)r間線。我們需要重點(diǎn)關(guān)注幾個(gè)關(guān)鍵指標(biāo)(load、fcp、lcp)之前的線程占用情況,hover 在 load 之前這一黃色色塊上,我們發(fā)現(xiàn) sdk 在 load 之前執(zhí)行了 30ms,成為了拖慢了業(yè)務(wù)指標(biāo)的原因之一。
此處截圖省略了一些內(nèi)部信息,一般情況下,如果需要更多信息可以借助 Source 模塊來(lái)找到引起主線程密集計(jì)算的代碼位置。
在這個(gè)例子中,這個(gè)調(diào)用未觸發(fā) longtask,并且我們很容易發(fā)現(xiàn)這就是 SDK 初始化的邏輯,也是接下來(lái)需要優(yōu)化的地方。
問(wèn)題分析與性能優(yōu)化
通過(guò)上述 benchmark 工具和 perfsee lab 性能分析結(jié)果,我們可以看出 SDK 初始化邏輯以及大量的事件監(jiān)聽(tīng)確實(shí)對(duì)業(yè)務(wù)性能造成了一定影響。
例如上文火焰圖中所示每一個(gè) onBFCacheRestore 都占用了超過(guò) 15ms 的時(shí)間,我們?cè)谠创a里搜索這個(gè)函數(shù),此部分偽代碼如下:
BFCache (https://web.dev/bfcache/) 即 back-forward cache,可稱為“往返緩存”,可以在用戶使用瀏覽器的“后退”和“前進(jìn)”按鈕時(shí)加快頁(yè)面的轉(zhuǎn)換速度。這個(gè)緩存不僅保存頁(yè)面數(shù)據(jù),還保存了 DOM 和 JS 的狀態(tài),實(shí)際上是將整個(gè)頁(yè)面都保存在內(nèi)存里。如果頁(yè)面位于 BFCache 中,那么再次打開(kāi)該頁(yè)面就不會(huì)觸發(fā) onload 事件。
可以看到,耗時(shí)主要由 onBFCacheRestore 和 onHidden 兩個(gè)方法中的原生 addEventListener 造成。這些監(jiān)聽(tīng)本身都是在毫秒級(jí)的,回調(diào)函數(shù)也沒(méi)有什么優(yōu)化空間,從實(shí)際場(chǎng)景考慮,這兩處回調(diào)是為了監(jiān)聽(tīng)用戶頁(yè)面前進(jìn)和返回的,并非優(yōu)先級(jí)最高的任務(wù)。
我們可以從以下幾個(gè)方面降低對(duì)業(yè)務(wù)造成的影響:
1. 監(jiān)控任務(wù)切片運(yùn)行,區(qū)分優(yōu)先級(jí)
對(duì)于監(jiān)控 SDK 而言,除了必要的監(jiān)聽(tīng)以及事件預(yù)收集等任務(wù),其他任何任務(wù)不應(yīng)該阻礙到業(yè)務(wù)代碼的執(zhí)行。對(duì)于字節(jié)前端監(jiān)控需求而言,異常和請(qǐng)求監(jiān)聽(tīng)為必須前置執(zhí)行的任務(wù),其他所有事件監(jiān)聽(tīng)可以拆分為單獨(dú)的任務(wù),所有的采樣、數(shù)據(jù)運(yùn)算、上報(bào)請(qǐng)求等數(shù)據(jù)后處理邏輯只在空閑時(shí)執(zhí)行,通過(guò) requestIdleCallback 調(diào)用。
2. 減少重復(fù)監(jiān)聽(tīng)次數(shù)
多個(gè)性能指標(biāo)監(jiān)聽(tīng)同一事件的公用監(jiān)聽(tīng)器,例如 CLS 和 LCP 這兩個(gè)指標(biāo)都需要監(jiān)聽(tīng) onBFCacheRestore,讓他們只做一次 addEventListener。
3. 請(qǐng)求數(shù)量的優(yōu)化
我們 SDK 的腳本是由一個(gè)必須最先執(zhí)行的主腳本(包含預(yù)收集、請(qǐng)求hook、錯(cuò)誤監(jiān)聽(tīng)等邏輯)和多個(gè)通過(guò)不同配置開(kāi)啟的異步插件腳本(性能、資源、白屏等)組成,主腳本的請(qǐng)求無(wú)法省略,而插件腳本可以通過(guò)接入 cdn combo 服務(wù)或自行搭建 combo 服務(wù)將多個(gè)請(qǐng)求合并成一個(gè)。
- 對(duì)于事件上報(bào)請(qǐng)求,我們?cè)趦?nèi)部維護(hù)一個(gè)緩存,只有當(dāng)間隔達(dá)到一定時(shí)間或者累計(jì)一定數(shù)量之后才會(huì)統(tǒng)一上報(bào)。在這個(gè)場(chǎng)景中,我們又需要考慮兩個(gè)問(wèn)題:
- 瀏覽器對(duì)請(qǐng)求并發(fā)量有限制,所以存在網(wǎng)絡(luò)資源競(jìng)爭(zhēng)的可能性
- 瀏覽器在頁(yè)面卸載時(shí)會(huì)忽略異步ajax請(qǐng)求,而同步 ajax 通常在現(xiàn)代瀏覽器中已被禁用
我們可以通過(guò)使用 navigator.sendBeacon 方法解決上述問(wèn)題。
這個(gè)方法主要用于滿足統(tǒng)計(jì)和診斷代碼的需要,這些代碼通常嘗試在卸載(unload)文檔之前向 Web 服務(wù)器發(fā)送數(shù)據(jù)。過(guò)早的發(fā)送數(shù)據(jù)可能導(dǎo)致錯(cuò)過(guò)收集數(shù)據(jù)的機(jī)會(huì)。然而,對(duì)于開(kāi)發(fā)者來(lái)說(shuō)保證在文檔卸載期間發(fā)送數(shù)據(jù)一直是一個(gè)困難。因?yàn)橛脩舸硗ǔ?huì)忽略在 unload (en-US)? 事件處理器中產(chǎn)生的異步 XMLHttpRequest
經(jīng)過(guò)以上優(yōu)化后,我們注入優(yōu)化過(guò)后的 SDK 再次跑分。
優(yōu)化后的 SDK 對(duì)業(yè)務(wù) FCP、LCP、LOAD 等性能的影響已經(jīng)降到了最低,已經(jīng)達(dá)到了非常高的性能標(biāo)準(zhǔn)。