從一個假死頁面引發的思考: 作為前端開發,除了要攻剋頁面難點,也要有更深的自我目標,性能優化是自我提升中很重要的一環; 在前端開發中,會偶遇到頁面假死的現象, 是因為當js有大量計算時,會造成 UI 阻塞,出現界面卡頓、掉幀等情況,嚴重時會出現頁面卡死的情況; ...
一、引言
從一個假死頁面引發的思考: 作為前端開發,除了要攻剋頁面難點,也要有更深的自我目標,性能優化是自我提升中很重要的一環; 在前端開發中,會偶遇到頁面假死的現象, 是因為當js有大量計算時,會造成 UI 阻塞,出現界面卡頓、掉幀等情況,嚴重時會出現頁面卡死的情況;
在這裡簡單穿插概念之進程和線程
- 進程:一個在記憶體中運行的應用程式。每個進程都有自己獨立的一塊記憶體空間,一個進程可以有多個線程,比如在Windows系統中,一個運行的demo.exe就是一個進程。
- 線程:進程中的一個執行任務(控制單元),負責當前進程中程式的執行。一個進程至少有一個線程,一個進程可以運行多個線程,多個線程可共用數據。與進程不同的是同類的多個線程共用進程的堆和方法區資源,但每個線程有自己的程式計數器、虛擬機棧和本地方法棧,所以系統在產生一個線程,或是在各個線程之間作切換工作時,負擔要比進程小得多,也正因為如此,線程也被稱為輕量級進程。
進程與線程的區別
線程具有許多傳統進程所具有的特征,故又稱為輕型進程(Light—Weight Process)或進程元;而把傳統的進程稱為重型進程(Heavy—Weight Process),它相當於只有一個線程的任務。在引入了線程的操作系統中,通常一個進程都有若幹個線程,至少包含一個線程。
◦根本區別:進程是操作系統資源分配的基本單位,而線程是處理器任務調度和執行的基本單位;
◦資源開銷:每個進程都有獨立的代碼和數據空間(程式上下文),程式之間的切換會有較大的開銷;線程可以看做輕量級的進程,同一類線程共用代碼和數據空間,每個線程都有自己獨立的運行棧和程式計數器(PC),線程之間切換的開銷小。
◦包含關係:如果一個進程內有多個線程,則執行過程不是一條線的,而是多條線(線程)共同完成的;線程是進程的一部分,所以線程也被稱為輕權進程或者輕量級進程。
◦記憶體分配:同一進程的線程共用本進程的地址空間和資源,而進程之間的地址空間和資源是相互獨立的;
◦影響關係:一個進程崩潰後,在保護模式下不會對其他進程產生影響,但是一個線程崩潰整個進程都死掉。所以多進程要比多線程健壯。
◦執行過程:每個獨立的進程有程式運行的入口、順序執行序列和程式出口。但是線程不能獨立執行,必須依存在應用程式中,由應用程式提供多個線程執行控制,兩者均可併發執行;
=> 回到阻塞原因分析
瀏覽器的渲染進程的線程:
瀏覽器有GUI渲染線程與JS引擎線程,這兩個線程是互斥的關係,當JS引擎執行時GUI線程會被掛起(相當於被凍結了),GUI更新會被保存在一個隊列中,等到JS引擎空閑時,立即被執行。js引擎不是每次在執行更新dom語句時,都會停下來等Gui渲染引擎更新完dom再執行後面的js代碼; 詳見GUI渲染線程與JS引擎線程
- GUI渲染線程: 負責渲染瀏覽器頁面,解析HTML、CSS,構建DOM樹、構建CSSOM樹、構建渲染樹和繪製頁面;當界面需要重繪或由於某種操作引發迴流時,該線程就會執行。註意:GUI渲染線程和JS引擎線程是互斥的,當JS引擎執行時GUI線程會被掛起,GUI更新會被保存在一個隊列中等到JS引擎空閑時立即被執行。
- JS引擎線程: JS引擎線程也稱為JS內核,負責處理Javascript腳本程式,解析Javascript腳本,運行代碼;JS引擎線程一直等待著任務隊列中任務的到來,然後加以處理,一個Tab頁中無論什麼時候都只有一個JS引擎線程在運行JS程式;註意:GUI渲染線程與JS引擎線程的互斥關係,所以如果JS執行的時間過長,會造成頁面的渲染不連貫,導致頁面渲染載入阻塞。
- 事件觸發線程:事件觸發線程屬於瀏覽器而不是JS引擎,用來控制事件迴圈;當JS引擎執行代碼塊如setTimeOut時(也可是來自瀏覽器內核的其他線程,如滑鼠點擊、AJAX非同步請求等),會將對應任務添加到事件觸發線程中;當對應的事件符合觸發條件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理;註意:由於JS的單線程關係,所以這些待處理隊列中的事件都得排隊等待JS引擎處理(當JS引擎空閑時才會去執行);
- 定時器觸發線程: 定時器觸發線程即setInterval與setTimeout所線上程;瀏覽器定時計數器並不是由JS引擎計數的,因為JS引擎是單線程的,如果處於阻塞線程狀態就會影響記計時的準確性;因此使用單獨線程來計時並觸發定時器,計時完畢後,添加到事件隊列中,等待JS引擎空閑後執行,所以定時器中的任務在設定的時間點不一定能夠準時執行,定時器只是在指定時間點將任務添加到事件隊列中;註意:W3C在HTML標準中規定,定時器的定時時間不能小於4ms,如果是小於4ms,則預設為4ms。
- 非同步http請求線程
二、案例分析
- 背景: 測試流水線開發過程中,為了滿足用戶操作方便,從節點中提取不同的指標來作為查詢條件的指標來源和數據來源; 因此用戶的每次操作都會進行重新計算,引起迴流; 由於前端組件父子嵌套層級很多,在數據過多的時候也會形成頁面假死的狀態。
- 基於不同的性能工具檢測;
1、performance Api
錄製步驟: 刷新頁面載入流水線詳情頁面,然後找到某個用例技術端,進行結果更新;因此下麵的圖分3個部分,第一個是詳情頁渲染; 第二段是需要大量計算的用例渲染部分; 第三段是操作結果,數據重新刷新,重新計算和渲染的部分;
其中,FPS上方顯示紅色條的時候,意味著幀率特別低,用戶體驗特別不好。一般來說,綠色條越高,FPS越高。 分析性能圖標第一眼看FPS;性能面板底部,圖形圖表的色彩越多,意味著CPU性能已經達到極限。當我們看到CPU長時間處於最大值狀態,就需要考慮怎樣去優化
介面的監控 + 詳情頁渲染 + 用例渲染;
上面的Group面板非常有用。我們可以很清晰明瞭得分析按照活動,目錄,域,子域,URL和Frame進行分組的前端性能;
Bottom-Up: 是The Heavy (Bottom Up) view is available in the Bottom-Up tab,類似事件冒泡;
Call Tree:是And the Tree (Top Down) view is available in the Call Tree tab,類似事件捕獲;
更新結果,頁面重新繪製,計算;
黃色(Scripting):JavaScript執行
紫色(Rendering):樣式計算和佈局,即重排
藍色(Loading):網路通信和HTML解析
綠色(Painting):重繪
灰色(System):其它事件花費的時間
白色(Idle):空閑時間: 可能是同時請求了很多介面,promise.all需要等待所有介面返回成功後才會渲染頁面,idle時間變長了很多,瀏覽器一直在等待介面全部返回;
用例列表和添加按鈕部分產生了偏移, 紅色Layout Shift;
2、lightHouse分析數據;分數很低;
3、performace monitor: 載入過程中,CPU飆80%;
主要可改善指標如下:
- TBT: total Blocking Time總阻塞時間
- CLS:記錄了頁面上非預期的位移波動
相關名詞解析:https://web.dev/metrics/;
指標 | 指標解析 |
---|---|
Self Time | Self Time代表函數本身執行消耗時間 |
Total Time | Total Time則是函數本身消耗再加上在調用它的函數中消耗的總時間 |
Activity | 瀏覽器活動的意思 |
DOM GC | DOM垃圾回收 |
Timer Fried | 銷毀計時器 |
XMR Load | 非同步載入對象載入 |
Major GC | 清理年老區(Tenured space) |
Minor GC | 每次Minor GC只會清理年輕代 |
Run Microtasks | 運行微服務 |
Recalculate Style | |
HitTest | https://www.jianshu.com/p/f6aff12fc08b |
DCLDomContentloaded | 當 HTML 文檔被完全載入和解析完成之後,DOMContentLoaded 事件被觸發,無需等待樣式表、圖像和子框架的完成載入. |
SI (Speed Index) | 指標用於顯示頁面可見部分的顯示速度, 單位是時間, |
FPFirst Paint 首次繪製 | 首次繪製(FP)這個指標用於記錄頁面第一次繪製像素的時間,如顯示頁面背景色。FP不包含預設背景繪製,但包含非預設的背景繪製。 |
FCPFirst contentful paint | 首次內容繪製 (FCP): LCP是指頁面開始載入到最大文本塊內容或圖片顯示在頁面中的時間。如果 FP 及 FCP 兩指標在 2 秒內完成的話我們的頁面就算體驗優秀。 |
LCPLargest contentful paint | 最大內容繪製 (LCP): 用於記錄視窗內最大的元素繪製的時間,該時間會隨著頁面渲染變化而變化,因為頁面中的最大元素在渲染過程中可能會發生改變,另外該指標會在用戶第一次交互後停止記錄。官方推薦的時間區間,在 2.5 秒內表示體驗優秀 |
FID(First input delay) | 首次輸入延遲,FID(First Input Delay),記錄在 FCP 和 TTI 之間用戶首次與頁面交互時響應的延遲 |
TTITime to Interactive | 可交互時間 (TTI)首次可交互時間,TTI(Time to Interactive)。這個指標計算過程略微複雜,它需要滿足以下幾個條件:1、從 FCP 指標後開始計算2、持續 5 秒內無長任務(執行時間超過 50 ms)且無兩個以上正在進行中的 GET 請求往前回溯至 5 秒前的最後一個長任務結束的時間3、對於用戶交互(比如點擊事件),推薦的響應時間是 100ms 以內。那麼為了達成這個目標,推薦在空閑時間里執行任務不超過 50ms( W3C 也有這樣的標準規定),這樣能在用戶無感知的情況下響應用戶的交互,否則就會造成延遲感。 |
TBTTotal blocking Time | 總阻塞時間 (TBT)阻塞總時間,TBT(Total Blocking Time),記錄在 FCP 到 TTI 之間所有長任務的阻塞時間總和。 |
CLSCumulative Layout Shift | 記錄了頁面上非預期的位移波動。頁面渲染過程中突然插入一張巨大的圖片或者說點擊了某個按鈕突然動態插入了一塊內容等等相當影響用戶體驗的網站。這個指標就是為這種情況而生的,計算方式為:位移影響的面積 * 位移距離。 |
三、性能優化三大核心指標:LCP FID CLS
上述的指標太多了,哪些才是核心指標呢? Google 在2020年五月提出了網站用戶體驗的三大核心指標
1、Largest Contentful Paint (LCP)
LCP:最大內容繪製 , 代表了頁面的速度指標,雖然還存在其他的一些體現速度的指標,但LCP能體現的東西更多一些。一是指標實時更新,數據更精確,二是代表著頁面最大元素的渲染時間,通常來說頁面中最大元素的快速載入能讓用戶感覺性能還挺好。
最大元素:
◦ 標簽
◦ 在svg中的image標簽
◦
◦CSS background url()載入的圖片
◦包含內聯或文本的塊級元素
影響元素:
◦服務端響應時間----介面性能
◦Javascript和CSS引起的渲染卡頓 ✨✨✨---webWork.js計算問題
◦資源載入時間✨✨✨---CDN
◦客戶端渲染✨✨---SSR
2、First Input Delay (FID):
FID:首次輸入延遲, 代表了頁面的交互體驗指標,就是看用戶交互事件觸發到頁面響應中間耗時多少,如果其中有長任務發生的話那麼勢必會造成響應時間變長,推薦響應用戶交互在 100ms 以內。
影響因素
◦減少第三方代碼的影響
◦減少Javascript的執行時間
◦最小化主線程工作
◦減小請求數量和請求文件大小
3、Cumulative Layout Shift (CLS)
CLS代表了頁面的穩定指標,它能衡量頁面是否排版穩定。尤其在手機上這個指標更為重要,因為手機屏幕挺小,CLS值一大的話會讓用戶覺得頁面體驗做的很差。CLS的分數在0.1或以下,則為Good。
影響因素: 通過下麵的原則避免非預期佈局移動:
◦圖片或視屏元素有大小屬性,或者給他們保留一個空間大小,設置width、height,或者使用 unsized-media feature policy 。
◦不要在一個已存在的元素上面插入內容,除了相應用戶輸入。
◦使用animation或transition而不是直接觸發佈局改變。
四、如何使用性能檢測工具?
- Lighthouse(速度也比較快, 支持json指標產出和頁面html產出),在本地進行測量,根據報告給出的一些建議進行優化;---例如CLS,用這個就很方便, 滿足分值後或者通過評審後上線;
- 項目發佈上線後,我們可以使用PageSpeed Insights去看下線上的性能情況;
- 使用PageSpeed Insights,可以使用Chrome User Experience Report API去撈取線上過去28天的數據;
- 數據有異常,可使用DevTools工具進行具體代碼定位分析;---performance分析, 方案總結和優化
- 使用Search Console’s Core Web Vitals report查看網站功能整體情況;
- 使用Web Vitals擴展方便的看頁面核心指標情況;
五、頁面過程詳細解析
上述講了網路請求,渲染過程,那下麵我們看下一個簡單的頁面的整個過程是怎麼樣的;
- 導航階段,該階段主要是從網路進程接收 HTML 響應頭和 HTML 響應體。
- 解析 HTML 數據階段,該階段主要是將接收到的 HTML 數據轉換為 DOM 和 CSSOM。
- 生成可顯示的點陣圖階段,該階段主要是利用 DOM 和 CSSOM,經過計算佈局、生成層樹 (LayerTree)、生成繪製列表 (Paint)、完成合成等操作,生成最終的圖片。
1. 導航階段,請求 HTML 數據階段:
◦該任務的第一個子過程就是 Send request,該過程表示網路請求已被髮送。然後該任務進入了等待狀態。
◦接著由網路進程負責下載資源,當接收到響應頭的時候,該任務便執行 Receive Respone 過程,該過程表示接收到 HTTP 的響應頭了。
◦接著執行 DOM 事件:pagehide、visibilitychange 和 unload 等事件,如果你註冊了這些事件的回調函數,那麼這些回調函數會依次在該任務中被調用。
◦些事件被處理完成之後,那麼接下來就接收 HTML 數據了,這體現在了 Recive Data 過程,Recive Data 過程表示請求的數據已被接收,如果 HTML 數據過多,會存在多個 Receive Data 過程。
◦等到所有的數據都接收完成之後,渲染進程會觸發另外一個任務,該任務主要執行 Finish load 過程,該過程表示網路請求已經完成。
2. 解析 HTML 數據階段
其中一個主要的過程是 HTMLParser:解析的上個階段接收到的 HTML 數據。
◦在 ParserHTML 的過程中,如果解析到了 script 標簽,那麼便進入了腳本執行過程,也就是圖中的 Evalute Script。
◦DOM 生成完成之後,會觸發相關的 DOM 事件,比如:典型的 DOMContentLoaded,還有 readyStateChanged。
◦DOM 生成之後,ParserHTML 過程繼續計算樣式表,也就是 Reculate Style,這就是生成 CSSOM 的過程
3. 生成可顯示點陣圖階段
該階段需要經歷佈局 (Layout)、分層、繪製、合成等一系列操作:
在生成完了 DOM 和 CSSOM 之後,渲染主線程首先執行了一些 DOM 事件,諸如 readyStateChange、load、pageshow。
總而言之,大致過程如下:
◦首先執行佈局,這個過程對應圖中的 Layout。
◦然後更新層樹 (LayerTree),這個過程對應圖中的 Update LayerTree。
◦有了層樹之後,就需要為層樹中的每一層準備繪製列表了,這個過程就稱為 Paint。
◦準備每層的繪製列表之後,就需要利用繪製列表來生成相應圖層的點陣圖了,這個過程對應圖中的 Composite Layers。走到了這步,主線程的任務就完成了。
接下來主線程會將合成的任務完全教給合成線程來執行,下麵是具體的過程,也可以對照著 Composite、Raster 和 GPU 這三個指標來分析
- 首先主線程執行到 Composite Layers 過程之後,便會將繪製列表等信息提交給合成線程,合成線程的執行記錄你可以通過 Compositor 指標來查看。合成線程維護了一個 Raster 線程池,線程池中的每個線程稱為 Rasterize,用來執行光柵化操作,對應的任務就是 Rasterize Paint。當然光柵化操作並不是在 Rasterize 線程中直接執行的,而是在 GPU 進程中執行的,因此 Rasterize 線程需要和 GPU 線程保持通信。然後 GPU 生成圖像,最終這些圖層會被提交給瀏覽器進程,瀏覽器進程將其合成並最終顯示在頁面上。
六、性能優化實踐方案
對於前端應用來說,網路耗時、頁面載入耗時、腳本執行耗時、渲染耗時等耗時情況會影響用戶的等待時長,
而 CPU占用、記憶體占用、本地緩存占用等則可能會導致頁面卡頓甚至卡死。
因此,性能優化可以分別從耗時和資源占用兩方面來解決,也可以理解成時間和空間兩個方面。
時間角度(耗時)
在時間角度進行優化主要是減少耗時,瀏覽器在頁面載入的過程中,主要會進行以下的步驟:
- 網路請求相關(發起 HTTP 請求從服務端獲取頁面資源,包括 HTML/CSS/JS/圖片資源等)瀏覽器解析 HTML 和渲染頁面載入 Javascript 代碼時會暫停頁面渲染(包括解析到外部資源,會發起 HTTP 請求獲取並載入)
耗時優化的著手點:
1、網路請求優化: 目標在於減少網路資源的請求和載入耗時,如果考慮 HTTP 請求過程,顯然我們可以從幾個角度來進行優化:
- 請求鏈路:DNS 查詢、部署 CDN 節點、緩存等
對於請求鏈路,核心的方案常常包括使用緩存,比如 DNS 緩存、CDN 緩存、HTTP 緩存、後臺緩存等等,前端的話還可以考慮使用Service Worker、PWA等技術。使用緩存並非萬能藥,很多使用由於緩存的存在,我們在功能更新修複的時候還需要考慮緩存的情況。除此之外,還可以考慮使用HTTP/2、HTTP/3 等提升資源請求速度,以及對多個請求進行合併,減少通信次數;對請求進行功能變數名稱拆分,提升併發請求數量。
- 數據大小:代碼大小、圖片資源等
數據大小則主要考對請求資源進行合理的拆分(CSS、Javascript 腳本、圖片/音頻/視頻等)和壓縮,減少請求資源的體積,比如使用Tree-shaking、代碼分割、移除用不上的依賴項等。在請求資源返回後,瀏覽器會進行解析和載入,這個過程會影響頁面的可見時間,通過對首屏載入的優化,可有效地提升用戶體驗。
2、首屏載入優化: 首屏載入優化核心點在於兩部分:
◦將頁面內容儘快地展示給用戶,減少頁面白屏時間。
◦將用戶可操作的時間儘量提前,避免用戶無法操作的卡頓體驗。
我們的頁面也需要在客戶端進行展示,此時可充分利用客戶端的優勢:
◦配合客戶端進行資源預請求和預載入,比如使用預熱 Web 容器配合客戶端將資源和數據進行離線,可用於下一次頁面的快速渲染使用秒看技術,通過生成預覽圖片的方式提前將頁面內容提供給用戶除了首屏渲染以外,用戶在瀏覽器頁面過程中,也會觸發頁面的二次運算和渲染,此時需要進行渲染過程的優化
3、渲染過程優化:渲染過程的優化可以理解成首屏載入完成後,用戶的操作交互觸發的二次渲染。主要思路是減少用戶的操作等待時間,以及通過將頁面渲染幀率保持在 60FPS 左右,提升頁面交互和渲染的流暢度。包括但不限於以下方案:
◦使用資源預載入,提升空閑時間的資源利用率--preload
◦減少/合併 DOM 操作,減少瀏覽器渲染過程中的計算耗時
◦使用離屏渲染,在頁面不可見的地方提前進行渲染(比如 Canvas 離屏渲染)
◦通過合理使用瀏覽器 GPU 能力,提升瀏覽器渲染效率(比如使用 css transform 代替 Canvas 縮放繪製)
以上這些,是對常見的 Web 頁面渲染優化方案。對於運算邏輯複雜、計算量較大的業務邏輯,我們還需要進行計算/邏輯運行的提速。
什麼是重繪和迴流
迴流比重繪更加消耗性能,付出的代價更高。
◦recalculate style (style):結合DOM和CSSOM,確定各元素應用的CSS規則
◦layout:重新計算各元素位置來佈局頁面,也稱reflow
◦update layer tree (layer):更新渲染樹
◦paint:繪製各個圖層
◦composite layers (composite):把各個圖層合成為完整頁面
核心: 佈局會不會變! 迴流一定會導致重繪,重繪不一定導致迴流
1.重繪:簡單來說就是重新繪畫,當給一個元素更換顏色、更換背景,雖然不會影響頁面佈局,但是顏色或背景變了,就會重新渲染頁面,這就是重繪。
2.迴流: 當增加或刪除dom節點,或者給元素修改寬高時,會改變頁面佈局,那麼就會重新構造dom樹然後再次進行渲染,這就是迴流。
哪些會引起迴流呢?
- 改變dom元素的幾何屬性,常見的幾何屬性有 width、height、padding、margin、left、top、border 等等。
- 改變dom樹的結構,主要指的是增加或減少dom節點,移動等操作。
- 獲取一定特殊的屬性值,如屬性:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight 時,你就要註意了!除此之外:調用了 getComputedStyle 方法,也會觸發迴流。
總結
重繪不會引起dom結構和頁面佈局的變化,只是樣式的變化,有重繪不一定有迴流。
迴流則是會引起dom結構和頁面佈局的變化,有迴流就一定有重繪。
怎麼進行優化或減少?
- 多個屬性儘量使用簡寫,例如:boder可以代替boder-width、boder-color、boder-style
- 創建多個dom節點時,使用documentfragment創建
- 避免使用table佈局
- 避免設置多層內聯樣式,避免節點層級過多!!!
- 避免使用css表達式
- 將頻繁重繪或迴流的節點設置為圖層,圖層能夠阻止該節點的渲染行為影響到別的節點(例:will-change\video\iframe等標簽),瀏覽器會自動將該節點變為圖層
1.計算/邏輯運行提速
- 計算/邏輯運行速度優化的主要思路是“拆大為小、多路並行”,方式包括但不限於:
通過將 Javscript 大任務進行拆解,結合非同步任務的管理,避免出現長時間計算導致頁面卡頓的情況
將耗時長且非關鍵邏輯的計算拆離,比如使用 Web Worker
通過使用運行效率更高的方式,減少計算耗時,比如使用 Webassembly
通過將計算過程提前,減少計算等待時長,比如使用 AOT 技術
通過使用更優的演算法或是存儲結構,提升計算效率,比如 VSCode 使用紅黑樹優化文本緩衝區的計算
通過將計算結果緩存的方式,減少運算次數
空間角度(資源)
在做性能優化的時候,其實很多情況下會依賴時間換空間、空間換時間等方式,只能根據自己項目的實際情況做出取捨,選擇相對合適的一種方案去進行優化。
資源占用常見的優化方式包括:
1.合理使用緩存,不濫用用戶的緩存資源(比如瀏覽器緩存、IndexDB),及時進行緩存清理;
2.避免存在記憶體泄露,比如儘量避免全局變數的使用、及時解除引用等
3.避免複雜/異常的遞歸調用,導致調用棧的溢出
4.通過使用數據結構享元的方式,減少對象的創建,從而減少記憶體占用
七、性能提升解決方案實踐:
實踐1: 單線程到多線程的實踐
註意:new Worker(xxx.js)里的xxx.js必須和HTML文件同源必須在http/https協議下訪問HTML文件,不能用文件協議(類似file:///E:/wamp64/www/t.html 這種
因此我用mamp搭建的一個環境指向測試的文件夾;本地配置http://www.performance.com/進行測試;
主進程代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>web-worker小測試</title>
</head>
<body>
<!-- <script type="text/javascript" src="worker.js"></script> -->
<script>
// const work = new Worker('worker.js');
// work.postMessage('hello worker')
// work.onmessage = (e) => {
// console.log(`主進程收到了子進程發出的信息:${e.data}`);
// // 主進程收到了子進程發出的信息:你好,我是子進程!
// work.terminate();
// };
let cnt = 0;
for (let i = 0; i < 900000000; i += 1) {
cnt += 1;
}
console.log(cnt);
</script>
</body>
</html
worker.js
onmessage = (e) => {
console.log(`收到了主進程發出的信息:${e.data}`);
let cnt = 0;
for (let i = 0; i < 900000000; i += 1) {
cnt += 1;
}
console.log(cnt);
//收到了主進程發出的信息:hello worker
postMessage(`你好,我是子進程!${cnt}`);
}
在vue.js中的使用過程, 除了配置,其他同上;
1、npm install vue-worker
2、chainWebpack中進行配置:
config.module
.rule('worker')
.test(/\.worker\.js$/)
.use('worker-loader')
.loader('worker-loader')
.options({
inline: 'fallback',
});
config.module.rule('js').exclude.add(/\.worker\.js$/);
更新後,當前頁面代碼段中,計算300ms+ 提升,效果一般,主要還是dom節點過多引起的;
Web Worker的限制
1.在 Worker 線程的運行環境中沒有 window 全局對象,也無法訪問 DOM 對象
2.Worker中只能獲取到部分瀏覽器提供的 API,如定時器、navigator、location、XMLHttpRequest等
3.由於可以獲取XMLHttpRequest 對象,可以在 Worker 線程中執行ajax請求
4.每個線程運行在完全獨立的環境中,需要通過postMessage、 message事件機制來實現的線程之間的通信
計算的運算時長 - 通信時長 > 50ms,推薦使用Web Worker
實踐2, webpack配置方向
1.生產環境關閉productionSourceMap、css sourceMap
SourceMap就是當頁面出現某些錯誤,能夠定位到具體的某一行代碼,SourceMap就是幫你建立這個映射關係的,方便代碼調試;
關閉css sourceMap以後,js文件從55M降低到12M, 文件從650個減少至325個;
實踐3、網路請求優化-CDN資源引入
分析大文件, 目前項目的情況: js總計12.1M (325個項目); css 總計11.1M (249個項目),
◦element-ui年1.85M--涉及改造的有點多,暫時不更新;
◦Ecahrts 2.55M --cdn引入
◦handsontable 3.34M---線上引入後,由於handsontable-vue還會安裝handsontable, 因此需要把handsontable-vue也使用線上的方式,cdh資源可在https://www.jsdelivr.com/package/npm/@handsontable/vue中找到;
◦vue-json-editor 1.24M--沒找到線上js資源,建議組件引入的時候,直接使用json-editor;
◦將資源進行cdn方式引入;使用CDN引入以後,js總計9.3M(326個項目), css總計10.9(249個項目)
- 安裝webpack-bundle-analyzer
- vue.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
// 正式環境不打包公共js
let externals = {};
// 儲存cdn的文件
const cdn = {
css: [
'https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.css',
],
js: [],
};
// 正式環境才需要
// if (isProduction) {
externals = { // 排除打包的js
vue: 'Vue',
echarts: 'echarts',
vueHandsontable: 'vue-handsontable',
};
cdn.js = [
'https://cdn.bootcdn.net/ajax/libs/vue/2.6.11/vue.min.js', // vuejs
'https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.min.js',
'https://cdn.jsdelivr.net/npm/handsontable/dist/handsontable.full.min.js',
'https://cdn.jsdelivr.net/npm/@handsontable/[email protected]/dist/vue-handsontable.min.js',
];
// }
**************
configureWebpack: {
// 常用的公共js 排除掉,不打包 而是在index添加cdn,
externals,
plugins: [
new BundleAnalyzerPlugin(), // 分析打包大小使用預設配置
],
},
*************
chainWebpack: (config) => {
// 註入cdn變數 (打包時會執行)
config.plugin('html').tap((args) => {
args[0].cdn = cdn; // 配置cdn給插件
return args;
});
}
實踐4、通過 compression-webpack-plugin 插件把代碼壓縮為gzip。但是!需要伺服器支持webpack端 vue.config.js配置如下:
使用CDN引入以後,js總計9.3M(326個項目)=> 3.1M, css總計10.9(249個項目)--沒有發生變化
nginx的配置, 這篇文章很不錯https://www.cnblogs.com/wwjj4811/p/15847916.html, 這塊我沒線上測試;不過應該沒啥問題;
1、安裝 compression-webpack-plugin(vue2--npm install --save-dev [email protected])
2、const CompressionPlugin = require('compression-webpack-plugin');
3、chainWebpack中配置
if (isProduction) {
config.plugin('compressionPlugin').use(new CompressionPlugin({
test: /\.(js)$/, // 匹配文件名
threshold: 10240, // 對超過10k的數據壓縮
minRatio: 0.8,
deleteOriginalAssets: true, // 刪除源文件
}));
}
4、nginx中增加配置後重啟
gzip on; #開啟gzip功能
gzip_types *; #壓縮源文件類型,根據具體的訪問資源類型設定
gzip_comp_level 6; #gzip壓縮級別
gzip_min_length 1024; #進行壓縮響應頁面的最小長度,content-length
gzip_buffers 4 16K; #緩存空間大小
gzip_http_version 1.1; #指定壓縮響應所需要的最低HTTP請求版本
gzip_vary on; #往頭信息中添加壓縮標識
gzip_disable "MSIE [1-6]\."; #對IE6以下的版本都不進行壓縮
gzip_proxied off; #nginx作為反向代理壓縮服務端返回數據的條件
實踐5、tree-shaking
tree shaking:通常用於描述移除 JavaScript 上下文中的未引用代碼(dead-code)。它依賴於 ES2015 模塊系統中的靜態結構特性,例如import和export,是 webpack 4 版本,擴展的這個檢測能力;
使用tree-shaking以後,js總計3.1M(326個項目)=> 2.9M(315個項目), css總計10.9(249個項目)--384K(15個項目)
作者:京東零售 蘇文靜
來源:京東雲開發者社區 轉載請註明來源