前端性能優化二:現代瀏覽器javascript性能優化(1)

前端性能優化一:性能指標javascript

現代前端程序中,前端資源文件中佔絕大部分byte的是javascript。一個現代前端應用程序,javascript包中可能包含如下幾種類型的js文件html

  • 一個客戶端的框架(react/vue等等)或者一個UI框架(Element UI等等)
  • 單頁應用程序state管理解決方案(Redux,vuex等等)
  • Polyfill
  • 一些工具庫(lodash/Axios等等)

你發送到瀏覽器的js文件越大,你的加載時間就會越慢前端

100kb的js文件和100kb的圖片文件所消耗的性能是很是不一樣的

他們可能用了相同的時間下載,可是處理這兩種文件的過程會帶來很是不一樣的性能消耗vue

一個jpeg圖片文件下載之後須要被解碼,柵格圖像,而後渲染到屏幕上。而一個js文件可能花費了相同的時間下載了一個相同大小的js文件,接下來交由js引擎處理咱們的js。java

js引擎處理管道流程

從拿到你寫的js文件開始。js引擎解析(parse)你的源代碼將他轉換成抽象語法樹(AST)。基於這個AST,解析器開始生成字節碼(bytecode)。ok到這裏引擎已經能夠開始執行你的代碼了。react

可是當你的其中一塊代碼被執行的次數愈來愈多,js引擎會將這一塊代碼標記成熱代碼(hot),爲了讓這一塊代碼執行的更快,以前生成的字節碼(bytecode)會傳入優化器,優化器會根據以前運行的代碼狀況,作一些假設(好比根據以前的運行狀況猜想變量的類型都是Number類型),而後根據這些假設進行優化。若是由於以前優化時候作出的假設錯誤,性能優化器會將代碼去優化,從新執行以前未優化的bytecode。webpack

這個是一個大體的流程每一個瀏覽器的實現方式都相似可是會有一些區別,在V8中解析器叫作(Ignition),優化器叫作(TurboFan),Ignition生成完bytecode以後,在執行過程當中收集執行數據(profiling data),用於在代碼變hot以後,交由TurboFan,根據執行數據作出假設進行優化.ios

SpiderMonkey用於Firefox的內核執行方式有一些不一樣,他有兩個優化器,一個Baseline優化器生成輕度的優化代碼,IonMonkey優化器生成高度優化的代碼.若是優化器作的假設失敗,去優化爲Baseline輕度優化代碼。 Chakra用於Edge的內核他也是兩個優化器,分別爲SimpleJIT生成輕度優化代碼,FullJIT生成高度優化代碼。JavaScriptCore用於Safari和ReactNative的內核有三個優化器。web

爲何每一個引擎都有不一樣個數的優化器?

解析器能夠快速的生成字節碼(bytecode),可是bytecode的執行效率卻不是很高,另一方面優化器能夠生成用於高效執行的機器碼,可是生成優化代碼所須要的時間也會高一些。這是一個快速獲得執行代碼(bytecode)和獲得可以快速執行的代碼(機器碼)的一個取捨,另外機器碼所佔用的內存也會高一些。因此優化器在選擇是否要優化一塊代碼的時候是很是複雜的意見事情,舉個例子vuex

fun()
複製代碼

假設咱們的前端程序只有這一行代碼,那麼可想而知,咱們快速的獲得能夠執行的bytecode比獲得高度優化的機器碼所花費的時間要有意義的多。

for(let i = 0;i< 100000;i++){
    fun()
}
複製代碼

一樣的一個方法被放在循環裏面被重複執行10萬次,那麼雖然花費了多一點的時間獲得機器碼,可是由於要執行不少次,因此獲得高度優化的機器碼所花費的時間也是划算的。

由於以前提到太高度優化的代碼所佔用的內存也會高一些,那麼考慮是否優化某塊代碼的時候還要考慮運行終端的內存狀況,好比在內存很是小的手機上,優化器選擇是否優化某塊代碼就要謹慎的多。

那麼若是跑在服務器端Node.js的環境下,由於程序可能只啓動一次,可是須要運行屢次,那麼第一次啓動的時間對程序來講意義不是很大,相反的可以快速執行的機器碼就要有意義的多。

優化器須要根據當前終端的內存狀況和代碼使用狀況決定是否優化代碼

因此一些引擎選擇多個優化器,能夠更好的控制這個複雜度。

可是總的來講,這些引擎執行js代碼的順序是同樣的,架構也是同樣的(SoureCode->AST->Bytecode->MachineCode).

說了這麼多就是想說js是如今瀏覽器中最消耗性能的資源,不一樣設備和網絡環境所帶來的性能表現差別也比較大,終端性能越差,固然花的時間對應的也越多,因此發送到瀏覽器的js文件越小越好。

tl;dr:

只加載當前首屏須要的js文件

利用code split將文件拆分,給他們排出優先級,只加載首屏用戶須要的js文件,將其餘的js文件懶加載,可以較大程度的減小js文件的大小,可以顯著的加快loading時間和TTI的時間。

學會分析你的js包,把你不須要的js弄出去

開發者若是沒有這個意識,有很大的可能性將一整個工具庫打包到一個bundles裏面,可是其實你只用了其中一個方法。

tree sharking如今應該不是什麼新鮮的單詞了,最先由Rollup提出,後來由於webpack的影響力,發揚光大。

code split和tree sharking這裏就不展開介紹了,網上已經有不少好的文章。

若是你已經服務器渲染HTML,考慮一下其實你真的須要客服端的這個庫嗎?

這一條主要針對vue或者react的universal程序,也就是說你的程序同時在服務器端和客戶端依賴了vue或者react框架,先利用服務器端渲染進行html渲染,而後在轉爲前端路由。這的確很酷,可是你有沒有想過,當你已經在服務器端利用vue或者react的服務器端渲染將html生成了,在一些簡單的頁面其實你是不須要將vue或者react發送到客戶端的。Netfix:是一個美國的視頻網站,他們依賴了react,他們的開發者嘗試在註冊頁去掉了前端的react,只在服務器端渲染的時候依賴react渲染html,也就是說將react也按需加載了,將註冊頁的js大小減小了70%,大大的提升了性能。

利用tree sharking和code split將你的js文件減小了之後,咱們始終仍是有js文件的。那咱們還能作什麼呢?

選擇合適的方式引入js文件(preload,async)

當瀏覽器得到服務器發送的HTML標籤,開始從上到下進行解析。碰到js標籤,就會發送js請求,而後進行解析,編譯,執行。在進行這一系列動做的時候主線程都是堵塞的。可是如今咱們有了更多的選擇來引入一個js文件。

<link rel="preload" href="one.js" as="script">
複製代碼

新版本的瀏覽器會在接收到HTML之後,先快速的檢查一遍全部的html標籤,若是碰到了preload,他會直接去請求這個文件,而後根據as後面的文件類型,對文件進行處理,若是是js就會解析,編譯。那麼何時preload的js文件會執行呢?

<script src="one.js" ></script>
複製代碼

當你碰到正常的一個script標籤或者你能夠onload的時候直接讓他執行

<link rel="preload" as="script" href="one.js" onload="var script = document.createElement('script'); script.src = this.href; document.body.appendChild(script);">
複製代碼

不論哪一種方式,瀏覽器都能更早的請求js文件,而後執行的時候不須要等待本來都是堵塞主線程的下載,解析,編譯。 preload不止能夠提早loadjs還能夠用於其餘類型的文件,能夠得到所有的list

<script src="one.js" async></script>
複製代碼

若是你須要引入一些第三方的js文件,好比baidu統計之類的第三方庫。你可讓這個標籤打上async屬性,當瀏覽器看到這個屬性的時候,這個js就不會堵塞線程。而且在新版本的chrome瀏覽器裏,async標籤他的解析,編譯都會在子線程裏進行,這個對性能是很划算的。

儘可能多的利用瀏覽器平行能夠請求標籤的數量,聰明的打包你的js,合理的利用緩存

不一樣的瀏覽器不一樣版本可以同時發起平行請求的數量不一樣,若是http2的標準那麼能同時發送的請求就更多了。具體的數量咱們這裏就不討論了。能同時發送的請求越多,意味着咱們能夠儘量多的在一個頁面不一樣組件中,將能夠共用的代碼的打包到一塊兒,也不用花兩個階段去請求js。在打包的時候,儘量的把不怎麼改變的文件和可能會發生改變的文件分開來打包,這樣就能夠充分利用緩存。對於特別大的js打包文件,儘可能能夠在同時請求數容許的範圍裏將文件打包成大於等於50kb一個。

爲何是50kb?

新版本的chrome已經開始支持js文件stream解析,也就是說當你的js文件還沒徹底下載完畢的時候,js引擎就能夠分塊的將js stream放在工做子線程進行解析。可是單個文件必須大於50kb纔會使用stream parse,因此將一個大的js包,分開打包爲多個50kb的js包,平行請求,chrome會使用stream parse,而且將解析,編譯的工做放到子線程進行,這樣主線程就能夠響應交互請求或者解析HTML。 例如facebook,經過大約292個請求加載了大約6M的壓縮過的js包,他利用async和stream parse將子線程的利用率大大的提升。

facebook利用async和stream parse將子線程利用率大大提升

NOTE:inline Script和緩存中加載的js文件沒辦法使用stream parse。

長任務

js長任務在以前已經介紹過,一直堵塞主線程,致使主線程沒法處理用戶響應和其餘任務的超過50ms的任務,咱們認爲是長任務。

那麼怎麼能夠縮短長任務呢?基本的思路就是把任務切片,設置優先級將長任務分塊的去執行。 若是你使用的是vue或者react這種響應式的框架。多個組件同時須要渲染,會引起多個組件相繼出發init->render->patch,頗有可能出現一個超過50ms的長任務。那你要分析有些組件是否是能夠放到後面去渲染(好比不是首屏須要的組件),或者兩個組件分開時間去渲染。

結論

在現代瀏覽器中js文件仍然是加載時最消耗性能的文件。因此利用code split和tree sharking讓js文件儘量的小,只加載首屏須要的文件是有效提升性能的辦法。另外使用preload和async來得到js。學會分析js包,利用緩存和stream parse也能夠有效的提升性能。下一章咱們討論js另外的一些提升性能的辦法。

相關文章
相關標籤/搜索