深入理解前端性能監(jiān)控
在同樣的網(wǎng)絡(luò)環(huán)境下,有兩個(gè)同樣能滿足你的需求的網(wǎng)站,一個(gè)唰的一下就加載出來(lái)了,另一個(gè)白屏轉(zhuǎn)圈轉(zhuǎn)了半天內(nèi)容才出來(lái),如果讓你選擇,你會(huì)用哪一個(gè)?
頁(yè)面的性能問(wèn)題是前端開(kāi)發(fā)中一個(gè)重要環(huán)節(jié),但一直以來(lái)我們沒(méi)有比較好的手段,來(lái)檢測(cè)頁(yè)面的性能。直到W3C性能小組引入的新的API window.performance,目前IE9以上的瀏覽器都支持。它是一個(gè)瀏覽器中用于記錄頁(yè)面加載和解析過(guò)程中關(guān)鍵時(shí)間點(diǎn)的對(duì)象。放置在global環(huán)境下,通過(guò)JavaScript可以訪問(wèn)到它。
使用性能API
你可以通過(guò)以下方法來(lái)探測(cè)和兼容performance:
- var performance = window.performance ||
- window.msPerformance ||
- window.webkitPerformance;
- if (performance) {
- // 你的代碼
- }
先來(lái)了解一下performance的結(jié)構(gòu):
performance.memory是顯示此刻內(nèi)存占用情況,它是一個(gè)動(dòng)態(tài)值,其中:
usedJSHeapSize表示:JS 對(duì)象(包括V8引擎內(nèi)部對(duì)象)占用的內(nèi)存數(shù)
totalJSHeapSize表示:可使用的內(nèi)存
jsHeapSizeLimit表示:內(nèi)存大小限制
通常,usedJSHeapSize不能大于totalJSHeapSize,如果大于,有可能出現(xiàn)了內(nèi)存泄漏。
performance.navigation顯示頁(yè)面的來(lái)源信息,其中:
redirectCount表示:如果有重定向的話,頁(yè)面通過(guò)幾次重定向跳轉(zhuǎn)而來(lái),默認(rèn)為0
type表示頁(yè)面打開(kāi)的方式,
0 表示 TYPE_NAVIGATENEXT 正常進(jìn)入的頁(yè)面(非刷新、非重定向等)
1 表示 TYPE_RELOAD 通過(guò) window.location.reload() 刷新的頁(yè)面
2 表示 TYPE_BACK_FORWARD 通過(guò)瀏覽器的前進(jìn)后退按鈕進(jìn)入的頁(yè)面(歷史記錄)
255 表示 TYPE_UNDEFINED 非以上方式進(jìn)入的頁(yè)面
performance.onresourcetimingbufferfull 屬性是一個(gè)在resourcetimingbufferfull事件觸發(fā)時(shí)會(huì)被調(diào)用的 event handler 。它的值是一個(gè)手動(dòng)設(shè)置的回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)會(huì)在瀏覽器的資源時(shí)間性能緩沖區(qū)滿時(shí)執(zhí)行。
performance.timeOrigin是一系列時(shí)間點(diǎn)的基準(zhǔn)點(diǎn),精確到萬(wàn)分之一毫秒。
performance.timing是一系列關(guān)鍵時(shí)間點(diǎn),它包含了網(wǎng)絡(luò)、解析等一系列的時(shí)間數(shù)據(jù)。
下面是對(duì)這些時(shí)間點(diǎn)進(jìn)行解釋
- timing: {
- // 同一個(gè)瀏覽器上一個(gè)頁(yè)面卸載(unload)結(jié)束時(shí)的時(shí)間戳。如果沒(méi)有上一個(gè)頁(yè)面,這個(gè)值會(huì)和fetchStart相同。
- navigationStart: 1543806782096,
- // 上一個(gè)頁(yè)面unload事件拋出時(shí)的時(shí)間戳。如果沒(méi)有上一個(gè)頁(yè)面,這個(gè)值會(huì)返回0。
- unloadEventStart: 1543806782523,
- // 和 unloadEventStart 相對(duì)應(yīng),unload事件處理完成時(shí)的時(shí)間戳。如果沒(méi)有上一個(gè)頁(yè)面,這個(gè)值會(huì)返回0。
- unloadEventEnd: 1543806782523,
- // 第一個(gè)HTTP重定向開(kāi)始時(shí)的時(shí)間戳。如果沒(méi)有重定向,或者重定向中的一個(gè)不同源,這個(gè)值會(huì)返回0。
- redirectStart: 0,
- // 最后一個(gè)HTTP重定向完成時(shí)(也就是說(shuō)是HTTP響應(yīng)的最后一個(gè)比特直接被收到的時(shí)間)的時(shí)間戳。
- // 如果沒(méi)有重定向,或者重定向中的一個(gè)不同源,這個(gè)值會(huì)返回0.
- redirectEnd: 0,
- // 瀏覽器準(zhǔn)備好使用HTTP請(qǐng)求來(lái)獲取(fetch)文檔的時(shí)間戳。這個(gè)時(shí)間點(diǎn)會(huì)在檢查任何應(yīng)用緩存之前。
- fetchStart: 1543806782096,
- // DNS 域名查詢開(kāi)始的UNIX時(shí)間戳。
- //如果使用了持續(xù)連接(persistent connection),或者這個(gè)信息存儲(chǔ)到了緩存或者本地資源上,這個(gè)值將和fetchStart一致。
- domainLookupStart: 1543806782096,
- // DNS 域名查詢完成的時(shí)間.
- //如果使用了本地緩存(即無(wú) DNS 查詢)或持久連接,則與 fetchStart 值相等
- domainLookupEnd: 1543806782096,
- // HTTP(TCP) 域名查詢結(jié)束的時(shí)間戳。
- //如果使用了持續(xù)連接(persistent connection),或者這個(gè)信息存儲(chǔ)到了緩存或者本地資源上,這個(gè)值將和 fetchStart一致。
- connectStart: 1543806782099,
- // HTTP(TCP) 返回瀏覽器與服務(wù)器之間的連接建立時(shí)的時(shí)間戳。
- // 如果建立的是持久連接,則返回值等同于fetchStart屬性的值。連接建立指的是所有握手和認(rèn)證過(guò)程全部結(jié)束。
- connectEnd: 1543806782227,
- // HTTPS 返回瀏覽器與服務(wù)器開(kāi)始安全鏈接的握手時(shí)的時(shí)間戳。如果當(dāng)前網(wǎng)頁(yè)不要求安全連接,則返回0。
- secureConnectionStart: 1543806782162,
- // 返回瀏覽器向服務(wù)器發(fā)出HTTP請(qǐng)求時(shí)(或開(kāi)始讀取本地緩存時(shí))的時(shí)間戳。
- requestStart: 1543806782241,
- // 返回瀏覽器從服務(wù)器收到(或從本地緩存讀取)第一個(gè)字節(jié)時(shí)的時(shí)間戳。
- //如果傳輸層在開(kāi)始請(qǐng)求之后失敗并且連接被重開(kāi),該屬性將會(huì)被數(shù)制成新的請(qǐng)求的相對(duì)應(yīng)的發(fā)起時(shí)間。
- responseStart: 1543806782516,
- // 返回瀏覽器從服務(wù)器收到(或從本地緩存讀取,或從本地資源讀?。┳詈笠粋€(gè)字節(jié)時(shí)
- //(如果在此之前HTTP連接已經(jīng)關(guān)閉,則返回關(guān)閉時(shí))的時(shí)間戳。
- responseEnd: 1543806782537,
- // 當(dāng)前網(wǎng)頁(yè)DOM結(jié)構(gòu)開(kāi)始解析時(shí)(即Document.readyState屬性變?yōu)?ldquo;loading”、相應(yīng)的 readystatechange事件觸發(fā)時(shí))的時(shí)間戳。
- domLoading: 1543806782573,
- // 當(dāng)前網(wǎng)頁(yè)DOM結(jié)構(gòu)結(jié)束解析、開(kāi)始加載內(nèi)嵌資源時(shí)(即Document.readyState屬性變?yōu)?ldquo;interactive”、相應(yīng)的readystatechange事件觸發(fā)時(shí))的時(shí)間戳。
- domInteractive: 1543806783203,
- // 當(dāng)解析器發(fā)送DOMContentLoaded 事件,即所有需要被執(zhí)行的腳本已經(jīng)被解析時(shí)的時(shí)間戳。
- domContentLoadedEventStart: 1543806783203,
- // 當(dāng)所有需要立即執(zhí)行的腳本已經(jīng)被執(zhí)行(不論執(zhí)行順序)時(shí)的時(shí)間戳。
- domContentLoadedEventEnd: 1543806783216,
- // 當(dāng)前文檔解析完成,即Document.readyState 變?yōu)?nbsp;'complete'且相對(duì)應(yīng)的readystatechange 被觸發(fā)時(shí)的時(shí)間戳
- domComplete: 1543806783796,
- // load事件被發(fā)送時(shí)的時(shí)間戳。如果這個(gè)事件還未被發(fā)送,它的值將會(huì)是0。
- loadEventStart: 1543806783796,
- // 當(dāng)load事件結(jié)束,即加載事件完成時(shí)的時(shí)間戳。如果這個(gè)事件還未被發(fā)送,或者尚未完成,它的值將會(huì)是0.
- loadEventEnd: 1543806783802
- }
這些參數(shù)非常有用,可以幫助我們獲取頁(yè)面的Domready時(shí)間、onload時(shí)間、白屏?xí)r間等,以及單個(gè)頁(yè)面資源在從發(fā)送請(qǐng)求到獲取到rsponse各階段的性能參數(shù)。
對(duì)我們比較有用的頁(yè)面性能數(shù)據(jù)大概包括如下幾個(gè),這些參數(shù)是通過(guò)上面的performance.timing各個(gè)屬性的差值組成的,它是精確到毫秒的一個(gè)值,計(jì)算方法如下:
- 重定向耗時(shí):redirectEnd - redirectStart
- DNS查詢耗時(shí) :domainLookupEnd - domainLookupStart
- TCP鏈接耗時(shí) :connectEnd - connectStart
- HTTP請(qǐng)求耗時(shí) :responseEnd - responseStart
- 解析dom樹(shù)耗時(shí) : domComplete - domInteractive
- 白屏?xí)r間 :responseStart - navigationStart
- DOMready時(shí)間 :domContentLoadedEventEnd - navigationStart
- onload時(shí)間:loadEventEnd - navigationStart,也即是onload回調(diào)函數(shù)執(zhí)行的時(shí)間。
如何優(yōu)化?
重定向優(yōu)化:重定向的類(lèi)型分三種,301(永久重定向),302(臨時(shí)重定向),304(Not Modified)。304是用來(lái)優(yōu)化緩存,非常有用,而前兩種應(yīng)該盡可能的避免,凡是遇到需要重定向跳轉(zhuǎn)代碼的代碼,可以把重定向之后的地址直接寫(xiě)到前端的html或JS中,可以減少客戶端與服務(wù)端的通信過(guò)程,節(jié)省重定向耗時(shí)。
DNS優(yōu)化:一般來(lái)說(shuō),在前端優(yōu)化中與 DNS 有關(guān)的有兩點(diǎn): 一個(gè)是減少DNS的請(qǐng)求次數(shù),另一個(gè)就是進(jìn)行DNS預(yù)獲?。≒refetching ) 。典型的一次DNS解析需要耗費(fèi) 20-120 毫秒(移動(dòng)端會(huì)更慢),減少DNS解析的次數(shù)是個(gè)很好的優(yōu)化方式,盡量把各種資源放在一個(gè)cdn域名上。DNS Prefetching 是讓具有此屬性的域名不需要用戶點(diǎn)擊鏈接就在后臺(tái)解析,而域名解析和內(nèi)容載入是串行的網(wǎng)絡(luò)操作,所以這個(gè)方式能減少用戶的等待時(shí)間,提升用戶體驗(yàn) 。新版的瀏覽器會(huì)對(duì)頁(yè)面中和當(dāng)前域名(正在瀏覽網(wǎng)頁(yè)的域名)不在同一個(gè)域的域名進(jìn)行預(yù)獲取,并且緩存結(jié)果,這就是隱式的 DNS Prefetch。如果想對(duì)頁(yè)面中沒(méi)有出現(xiàn)的域進(jìn)行預(yù)獲取,那么就要使用顯示的 DNS Prefetch 了。下圖是DNS Prefetch的方法:
- <html>
- <head>
- <title>騰訊網(wǎng)</title>
- <link rel="dns-prefetch" href="//mat1.gtimg.com" />
- <link rel="dns-prefetch" href="//inews.gtimg.com" />
- <link rel="dns-prefetch" href="//wx.qlogo.cn" />
- <link rel="dns-prefetch" href="//coral.qq.com" />
- <link rel="dns-prefetch" href="//pingjs.qq.com" />
TCP請(qǐng)求優(yōu)化:TCP的優(yōu)化大都在服務(wù)器端,前端能做的就是盡量減少TCP的請(qǐng)求數(shù),也就是減少HTTP的請(qǐng)求數(shù)量。http 1.0 默認(rèn)使用短連接,也是TCP的短連接,也就是客戶端和服務(wù)端每進(jìn)行一次http操作,就建立一次連接,任務(wù)結(jié)束就中斷連接。這個(gè)過(guò)程中有3次TCP請(qǐng)求握手和4次TCP請(qǐng)求釋放。減少TCP請(qǐng)求的方式有兩種,一種是資源合并,對(duì)于頁(yè)面內(nèi)的圖片、css和js進(jìn)行合并,減少請(qǐng)求量。另一種使用長(zhǎng)鏈接,使用http1.1,在HTTP的響應(yīng)頭會(huì)加上 Connection:keep-alive,當(dāng)一個(gè)網(wǎng)頁(yè)打開(kāi)完成之后,連接不會(huì)馬上關(guān)閉,再次訪問(wèn)這個(gè)服務(wù)時(shí),會(huì)繼續(xù)使用這個(gè)長(zhǎng)連接。這樣就大大減少了TCP的握手次數(shù)和釋放次數(shù)。或者使用Websocket進(jìn)行通信,全程只需要建立一次TCP鏈接。
HTTP請(qǐng)求優(yōu)化:使用內(nèi)容分發(fā)網(wǎng)絡(luò)(CDN)和減少請(qǐng)求。使用CDN可以減少網(wǎng)絡(luò)的請(qǐng)求時(shí)延,CDN的域名不要和主站的域名一樣,這樣會(huì)防止訪問(wèn)CDN時(shí)還攜帶主站cookie的問(wèn)題,對(duì)于網(wǎng)絡(luò)請(qǐng)求,可以使用fetch發(fā)送無(wú)cookie的請(qǐng)求,減少http包的大小。也可以使用本地緩存策略,盡量減少對(duì)服務(wù)器數(shù)據(jù)的重復(fù)獲取。
渲染優(yōu)化:在瀏覽器端的渲染過(guò)程,如大型框架,vue和react,它的模板其實(shí)都是在瀏覽器端進(jìn)行渲染的,不是直出的html,而是要走框架中相關(guān)的框架代碼才能去渲染出頁(yè)面,這個(gè)渲染過(guò)程對(duì)于首屏就有較大的損耗,白屏的時(shí)間會(huì)有所增加。在必要的情況下可以在服務(wù)端進(jìn)行整個(gè)html的渲染,從而將整個(gè)html直出到我們的瀏覽器端,而非在瀏覽器端進(jìn)行渲染。
還有一個(gè)問(wèn)題就是,在默認(rèn)情況下,JavaScript 執(zhí)行會(huì)“阻止解析器”,當(dāng)瀏覽器遇到一個(gè) script 外鏈標(biāo)記時(shí),DOM 構(gòu)建將暫停,會(huì)將控制權(quán)移交給 JavaScript 運(yùn)行時(shí),等腳本下載執(zhí)行完畢,然后再繼續(xù)構(gòu)建 DOM。而且內(nèi)聯(lián)腳本始終會(huì)阻止解析器,除非編寫(xiě)額外代碼來(lái)推遲它們的執(zhí)行。我們可以把 script 外鏈加入到頁(yè)面底部,也可以使用 defer 或 async 延遲執(zhí)行。defer 和 async 的區(qū)別就是 defer 是有序的,代碼的執(zhí)行按在html中的先后順序,而 async 是無(wú)序的,只要下載完畢就會(huì)立即執(zhí)行?;蛘呤褂卯惒降木幊谭椒ǎ热鐂ettimeout,也可以使用多線webworker,它們不會(huì)阻礙 DOM 的渲染。
- <script async type="text/javascript" src="app1.js"></script>
- <script defer type="text/javascript" src="app2.js"></script>
資源性能API
performance.timing記錄的是用于分析頁(yè)面整體性能指標(biāo)。如果要獲取個(gè)別資源(例如JS、圖片)的性能指標(biāo),就需要使用Resource Timing API。
performance.getEntries()方法,包含了所有靜態(tài)資源的數(shù)組列表;每一項(xiàng)是一個(gè)請(qǐng)求的相關(guān)參數(shù)有name,type,時(shí)間等等。下圖是chrome顯示騰訊網(wǎng)的相關(guān)資源列表。
可以看到,與 performance.timing 對(duì)比: 沒(méi)有與 DOM 相關(guān)的屬性,新增了name、entryType、initiatorType和duration四個(gè)屬性。它們是
- name表示:資源名稱(chēng),也是資源的絕對(duì)路徑,可以通過(guò)performance.getEntriesByName(name屬性的值),來(lái)獲取這個(gè)資源加載的具體屬性。
- entryType表示:資源類(lèi)型 "resource",還有“navigation”, “mark”, 和 “measure”另外3種。
- initiatorType表示:請(qǐng)求來(lái)源 "link",即表示<link> 標(biāo)簽,還有“script”即 <script>,“img”即<img>標(biāo)簽,“css”比如background的url方式加載資源以及“redirect”即重定向 等。
- duration表示:加載時(shí)間,是一個(gè)毫秒數(shù)字。
受同源策略影響,跨域資源獲取到的時(shí)間點(diǎn),通常為0,如果需要更詳細(xì)準(zhǔn)確的時(shí)間點(diǎn),可以單獨(dú)請(qǐng)求資源通過(guò)performance.timing獲得?;蛘哔Y源服務(wù)器開(kāi)啟響應(yīng)頭Timing-Allow-Origin,添加指定來(lái)源站點(diǎn),如下所示:
- Timing-Allow-Origin: https://qq.com
方法集合
除了performance.getEntries之外,performance還包含一系列有用的方法。如下圖
performance.now()
performance.now() 返回一個(gè)當(dāng)前頁(yè)面執(zhí)行的時(shí)間的時(shí)間戳,用來(lái)精確計(jì)算程序執(zhí)行時(shí)間。與 Date.now() 不同的是,它使用了一個(gè)浮點(diǎn)數(shù),返回了以毫秒為單位,小數(shù)點(diǎn)精確到微秒級(jí)別的時(shí)間,更加精準(zhǔn)。并且不會(huì)受系統(tǒng)程序執(zhí)行阻塞的影響,performance.now() 的時(shí)間是以恒定速率遞增的,不受系統(tǒng)時(shí)間的影響(系統(tǒng)時(shí)間可被人為或軟件調(diào)整)。performance.timing.navigationStart + performance.now() 約等于 Date.now()。
- let t0 = window.performance.now();
- doSomething();
- let t1 = window.performance.now();
- console.log("doSomething函數(shù)執(zhí)行了" + (t1 - t0) + "毫秒.")
通過(guò)這個(gè)方法,我們可以用來(lái)測(cè)試某一段代碼執(zhí)行了多少時(shí)間。
performance.mark()
mark方法用來(lái)自定義添加標(biāo)記時(shí)間。使用方法如下:
- var nameStart = 'markStart';
- var nameEnd = 'markEnd';
- // 函數(shù)執(zhí)行前做個(gè)標(biāo)記
- window.performance.mark(nameStart);
- for (var i = 0; i < n; i++) {
- doSomething
- }
- // 函數(shù)執(zhí)行后再做個(gè)標(biāo)記
- window.performance.mark(nameEnd);
- // 然后測(cè)量這個(gè)兩個(gè)標(biāo)記間的時(shí)間距離,并保存起來(lái)
- var name = 'myMeasure';
- window.performance.measure(name, nameStart, nameEnd);
保存后的值可以通過(guò) performance.getEntriesByname( 'myMeasure' )或者 performance.getEntriesByType('measure')查詢。
Performance.clearMeasures()
從瀏覽器的性能輸入緩沖區(qū)中移除自定義添加的 measure
Performance.getEntriesByName()
返回一個(gè) PerformanceEntry 對(duì)象的列表,基于給定的 name 和 entry type
Performance.getEntriesByType()
返回一個(gè) PerformanceEntry 對(duì)象的列表,基于給定的 entry type
Performance.measure()
在瀏覽器的指定 start mark 和 end mark 間的性能輸入緩沖區(qū)中創(chuàng)建一個(gè)指定名稱(chēng)的時(shí)間戳,見(jiàn)上例
Performance.toJSON()
是一個(gè) JSON 格式轉(zhuǎn)化器,返回 Performance 對(duì)象的 JSON 對(duì)象
資源緩沖區(qū)監(jiān)控
Performance.setResourceTimingBufferSize()
設(shè)置當(dāng)前頁(yè)面可緩存的最大資源數(shù)據(jù)個(gè)數(shù),entryType為resource的資源數(shù)據(jù)個(gè)數(shù)。超出時(shí),會(huì)清空所有entryType為resource的資源數(shù)據(jù)。參數(shù)為整數(shù)(maxSize)。配合performance.onresourcetimingbufferfull事件可以有效監(jiān)控資源緩沖區(qū)。當(dāng)entryType為resource的資源數(shù)量超出設(shè)置值的時(shí)候會(huì)觸發(fā)該事件。
Performance.clearResourceTimings()
從瀏覽器的性能數(shù)據(jù)緩沖區(qū)中移除所有的 entryType 是 "resource" 的 performance entries
下面是mdn上關(guān)于這個(gè)屬性的一個(gè)demo。這個(gè)demo的主要內(nèi)容是當(dāng)緩沖區(qū)內(nèi)容滿時(shí),調(diào)用buffer_full函數(shù)。
- function buffer_full(event) {
- console.log("WARNING: Resource Timing Buffer is FULL!");
- performance.setResourceTimingBufferSize(200);
- }
- function init() {
- // Set a callback if the resource buffer becomes filled
- performance.onresourcetimingbufferfull = buffer_full;
- }
- <body onload="init()">
使用performance的這些屬性和方法,能夠準(zhǔn)確的記錄下我們想要的時(shí)間,再加上日志采集等功能的輔助,我們就能很容易的掌握自己網(wǎng)站的各項(xiàng)性能指標(biāo)了。
兼容性
目前主流瀏覽器雖然都已支持performance對(duì)象,但是并不能支持它上面的全部屬性和方法,有些細(xì)微的差別。本文主要依據(jù)chrome和qq瀏覽器測(cè)試了相關(guān)屬性和方法,均可使用。
我們做了什么?(劃重點(diǎn))
現(xiàn)在的很多性能監(jiān)控分析工具都是通過(guò)數(shù)據(jù)上報(bào)來(lái)實(shí)現(xiàn)的,不能及時(shí)有效的反饋?lái)?yè)面的性能問(wèn)題,只能在用戶使用之后上報(bào)(問(wèn)題出現(xiàn)之后)才能知道。所以基于新聞前端團(tuán)隊(duì)基于performance API做了一款實(shí)時(shí)查看性能的的工具,它并能給出詳細(xì)的報(bào)表,在開(kāi)發(fā)階段把性能問(wèn)題給解決掉。
superProfiler【外部開(kāi)源流程中】
它是一款JavaScript性能監(jiān)控工具庫(kù),通過(guò)腳本引用,加載展示在頁(yè)面右側(cè),無(wú)須依賴(lài)任何庫(kù)和腳本,可以實(shí)時(shí)查看當(dāng)前頁(yè)面的FPS、代碼執(zhí)行耗時(shí)、內(nèi)存占用以及當(dāng)前頁(yè)面的網(wǎng)絡(luò)性能,資源占用。
還能查看最近的(10次)頁(yè)面性能的平均數(shù)。點(diǎn)擊“生成報(bào)表”按鈕會(huì)生成更詳細(xì)的數(shù)據(jù)報(bào)表概覽。
小結(jié)
Performance API 用來(lái)做前端性能監(jiān)控非常有用,它提供了很多方便測(cè)試我們程序性能的接口。比如mark和measure。很多優(yōu)秀的框架也用到了這個(gè)API進(jìn)行測(cè)試。它里面就頻繁用到了mark和measure來(lái)測(cè)試程序性能。所以想要開(kāi)發(fā)高性能的web程序,了解Performace API還是非常重要的。最后通過(guò)superProfiler工具可以更快更便捷的查找出性能問(wèn)題,針對(duì)性的擊破問(wèn)題,提高開(kāi)發(fā)效率,提升用戶體驗(yàn)。當(dāng)然這只是前端性能優(yōu)化的第一步,道阻且長(zhǎng)。希望大家提出問(wèn)題和指出疑問(wèn),一起進(jìn)步。