(四)代码优化 (快来看看怎样写出真正高性能的代码)
代碼優化
- JS開銷和如何縮短解析時間【為什么我的JS運行慢】
- js開銷在哪里
- 解決方案
- 減少主線程工作量
- Progressive Bootstrapping(漸進式啟動)
- 配合V8 有效優化代碼【路走對了才能快】
- V8編譯原理
- 抽象語法樹
- V8優化機制
- 函數優化
- 函數的解析方式
- 對象優化【JS對象避坑地圖】
- 對象優化可以做哪些
- HTML優化
- 借助工具
- CSS對性能的影響
- 樣式計算開銷
- CSS優化
JS開銷和如何縮短解析時間【為什么我的JS運行慢】
js開銷在哪里
- 加載
- 解析&編譯
- 執行
資源大小相同的情況下,js的開銷更高
170kb的js和jpg,通過網絡加載的時間一致(加載過程只是大小影響),但是js后面要經歷編譯解析(2s)、執行(1.5s),jpg要經歷解碼(64.9ms)、圖片繪制到頁面(0.028s)
summary里可以看到解析的是哪個腳本
Bottom-Up自下而上,是一個拆分,里面具體做了哪些事,耗時多久,解析好事1757ms,垃圾回收時間61.5ms,編譯1.6ms
對于一個網站而言,總共的網絡加載過程中,壓縮后1.4M的js在整個網絡加載耗時中占1/3
解決方案
- Code splitting代碼拆分,按需加載
當前訪問路徑需要哪些資源加載哪些資源,不需要的進行延遲,或者訪問需要它的頁面時再加載 - Tree shaking代碼減重
不用的代碼搖掉
減少主線程工作量
- 避免長任務
- 避免超過1kb的行間腳本
瀏覽器引擎沒辦法對行間腳本進行有效優化,行間腳本越大,解析消耗的時間就越長 - 使用rAF和rAC進行時間調度
Progressive Bootstrapping(漸進式啟動)
可見不可交互 vs 最小可交互資源集
配合V8 有效優化代碼【路走對了才能快】
V8編譯原理
V8是chrome瀏覽器的js引擎,它是目前做得最好、效率最高的js引擎,后臺nodejs也是采用v8引擎
瀏覽器或者v8引擎拿到js腳本后,首先進行parse it(解析),將它翻譯成抽象語法樹(AST),所有的編程語言都有這樣的過程,要把文本識別成字符,然后把重要信息提取出來,變成一些節點,存儲在一定的數據結構里,再利用數據結構理解你寫的內容是什么寓意,理解什么寓意是Interpreter(解釋器)要做的事,在把代碼編程機器碼去運行之前,編譯器會進行優化工作,這個編譯器是Optimising Compiler(有優化功能的編譯器),有時它做的自動優化工作并不一定合適,所以再運行時,發現所做優化不合適時,會發生逆優化、反優化的過程,把剛剛做的優化去掉,這樣的情況反而會降低我們的效率,所以在代碼層面做的優化是盡量滿足優化的條件,它怎么做優化,我們按照它期望的代碼去寫,回避造成它反優化過程的代碼
下面寫個逆優化代碼,運行在node環境下
代碼運行時間大概是54ms,注釋add(num1, ‘s’);再運行,代碼運行時間減少到19ms,看代碼就是add函數,雖然調了很多次,但是參數很穩定,每次都是兩個數相加,這兩個數都不變,所以在編譯過程中會對這個函數進行優化,如果打開add(num1, ‘s’),在某次執行函數時,發現參數類型發生變化,運行時不能用已經做過優化的邏輯了,要把剛做的優化撤銷掉,這樣會帶來一定的延遲
如果想進一步了解v8到底對什么做了優化,對什么做了反優化,可以利用node的兩個參數(trace-opt,trace-deopt)
抽象語法樹
- 源碼=>抽象語法樹=>字節碼Bytecode=>機器碼
- 編譯過程會進行優化
- 運行時可能發生反優化
V8優化機制
- 腳本流
腳本正常情況下要先下載再進行解析再執行的過程,chrome在這邊做了優化,在下載過程中也同時進行解析的話可以加快這個過程,下載一個腳本,當它超過30kb時,它就認為已經足夠大,可以對這30kb的內容先進行解析,會單獨開一個線程去給這段代碼進行解析,等整個都加載完成時,再進行解析時,效率就大大提高了,因為把前面的部分已經解析過了,把所有解析的內容合并下,然后就可以進行執行,這是流式處理的一個特點 - 字節碼緩存
如果有些東西使用頻率比較高,可以把它進行緩存,再次進行訪問時就可以加快訪問,源碼被翻譯成字節碼之后,發現有一些不僅在當前頁面有使用,在其他頁面也會使用的片段,把這些片段對應的字節碼緩存起來,在其他頁面再次訪問相同邏輯時,直接從緩存去取它,不需要再進行翻譯的過程,這樣效率就大大提高 - 懶解析
主要對于函數而言,雖然聲明了這個函數,不一定馬上會用它,默認情況下會進行懶解析,先不去解析函數內部的邏輯,當我真正要用時我再去解析函數聲明的函數體,不需要解析的話也不需要為它去創建語法樹,進一步而言,在我們堆的內存空間里也不用為這個函數進行內存的分配,這樣對性能是極大的提升
函數優化
函數的解析方式
- lazy parsing懶解析 vs eager parsing饑餓解析
不能否認懶解析作為默認的解析方式,可以極大提高js的整體效率,但是在現實中,有時還是希望函數立即執行,這樣會有什么問題?如果我們的函數是立即執行的,在剛開始聲明的時候,默認對它進行懶解析,但是我們尤發現它要立即執行,于是又進行快速的饑餓解析,這樣就對同一個函數先進行懶解析再進行饑餓解析,導致效率降低了一半,所以需要一種方式告訴我們的解析器,我這個函數需要立即執行,你現在就對它進行饑餓解析,接下來我們看下在代碼里怎么告訴解析器我的函數是需要進行eager parsing的,也進行性能的前后對比
npm start運行,分析發現test.bundle.js的解析時間大概是0.4ms
- 利用Optimize.js優化初次加載時間
js會進行壓縮,當用工具進行壓縮時,實際上又會幫我們把eager parsing的括號去掉,會導致我們本來想做的事沒辦法通知到解析器,為了處理和解決這個問題,有人做了Optimize.js這個工具,幫助我們在這種情況下把括號添加回來
對象優化【JS對象避坑地圖】
對象優化可以做哪些
做這些優化的根據是迎合V8引擎進行解析,把你的代碼進行優化,它也是用代碼寫的,它所做的優化其實也是代碼實現的一些規則,如果我們寫的代碼可以迎合這些規則,就可以幫你去優化,代碼效率可以得到提升
- 以相同順序初始化對象成員,避免隱藏類的調整
js是動態、弱類型語言,寫的時候不會聲明和強調它變量的類型,但是對于編輯器而言,實際上還是需要知道確定的類型,在解析時,它根據自己的推斷,它會給這些變量賦一個具體的類型,它有多達21種的類型,我們管這些類型叫隱藏類型(hidden class),之后它所做的優化都是基于hidden class進行的
- 實例化后避免添加新屬性
- 盡量使用Array代替array-like對象
array-like對象:js里都有一個arguments這樣的對象,它包含了函數參數變量的信息,本身是一個對象,但是可以通過索引去訪問里面的屬性,它還有length的屬性,像是一個數組,但它又不是數組,不具備數組帶的一些方法,比如說foreach
如果本身真的是數組,v8引擎會對這個數組進行極大性能的優化,只是array-like的話,它做不了這些事情,在調用array方法時,通過間接的手段可以達到遍歷array-like對象,但是效率沒有在真實數組上高
- 避免讀取超過數組的長度
這是講越界的問題,js里不容易發現這越界問題,越界了也不一定報錯
越界比較的話會造成沿原型鏈額外的查找,這個能相差到6倍
- 避免元素類型轉換
對于編輯器而言,實際上是有類型的
類型越具體,編輯器能做的優化就越多,如果變得越通用,能做的優化余地就越少
可以去v8官方看看技術博客,會經常更新它們的優化方案,我們如果可以不斷配合他們的優化方案,可以讓我們代碼的效率不斷提高
HTML優化
html優化空間比較小,html大小在整個頁面所有資源里占比比較小,但是也不能忽視,優化工作要做到極致,即使1kb也不能放棄,
在html里,有很多沒有用的空間,還有一些可以省略的元素,就類似上圖中的企鵝群,大家可以再擠一擠,擠在一起就可以達到優化的目的
-
減少iframes使用
額外添加了文檔,需要加載的過程,也會阻礙父文檔的加載過程,如果它加載不完成,父文檔本身的onload事件就不會觸發,一直等著它,在iframe里創建的元素,比在父文檔創建同樣的元素,開銷要高出很多;非要用iframe的話,可以做個延時加載,不要一上來就加載iframe,聲明一個iframe,在父文檔加載完成之后,再拿到iframe,再對src賦值,讓它做加載,達到延遲的目的,不會影響剛開始頁面的加載過程
-
壓縮空白符
編程的時候,為了方便閱讀,會留空白或者空行,這些空白符也是占空間的,最后打包時要把空白符去掉 -
避免節點深層級嵌套
嵌套越深消耗越高,節點越多最后生成dom樹占有內存會比較高,有個遍歷,嵌套越深遍歷就越慢
-
避免使用table布局
table布局本身有很多問題,使用起來沒有那么靈活,造成的開銷非常大,同樣實現一種布局的方式,用table布局開發和維護起來,相對而言都更麻煩 -
刪除注釋
把無效內容去掉,減少大小 -
CSS&Javascript盡量外鏈
CSS和Javascript直接寫在行間,會造成html文檔過大,對于引擎來說,后續也不好做優化,css和js有時確實要做在行間,這個和偷懶寫在行間是兩碼事 -
刪除元素默認屬性
本身默認那個值,沒有必要寫出來,寫出來就添加了額外的字符,要通過網絡傳送給客戶端,這就是一些浪費
head里有很多meta,每個meta要清楚對應的作用,沒有用的不要寫上去,都是浪費
css通過外部css進行引入
body部分多使用html5的語義標簽,方便瀏覽器理解你寫的內容是什么,可以進行相關的優化
有一些元素,前面有open tag,后面有closing tag,并不是所有元素需要closing tag,比如img、li
考慮可訪問性,video,瀏覽器支持或者不支持,還有支持的視頻格式都要進行考慮
js要放在body的尾部進行加載,為了防止影響dom的加載,js是阻塞的,如果開始就進行加載,它的加載解析就會影響后面dom的加載
借助工具
html-minifier
CSS對性能的影響
樣式計算開銷
- 利用DevTools測量樣式計算開銷
復雜度計算,降低計算的復雜度,對元素進行定義樣式,盡量定義單一的樣式類去描述它的樣式,盡量不要使用過于復雜的偽類,多層級聯,去鎖定這個元素進行樣式描述
css解析的原則是自右向左去讀,先會找出最具體的元素,把所有的a全都找出來,再根據#box進行過濾,再進行過濾,再進行過濾,直到把所有受到影響的元素全都過濾出來,然后運用這個樣式,隨著瀏覽器解析不斷進步,現在這種復雜度的計算已經不是最主要的問題
CSS優化
- 降低CSS對渲染的阻塞
由于CSS對渲染的阻塞是無法進行避免的,所以我們從兩個角度進行優化:1盡量早的完成css的下載,盡早的進行解析;2降低css的大小,首次加載時,只加載當前路徑或者首屏有用的css,用不到的進行推遲加載,把影響降到最低 - 利用GPU進行完成動畫
- 使用contain屬性
從上圖可以看出,沒有使用contain布局消耗的時間大概是56.89ms,使用之后可以降低到0.04ms,這是一個非常大的優化
contain有多個值,layout是其中一個,是現在目前主流瀏覽器支持比較好的值,作用也比較大
上圖是新聞的一個展示頁,如果想在第一條內容里插入其他一些內容,對于我們關鍵渲染路徑而言,瀏覽器并不能知道你插入的東西會不會影響到其他元素的布局,這個時候它就需要對這個頁面上的元素進行重新的檢查,重新的計算,開銷很大,這里有將近10000條的新聞,將近10000個元素要受到影響,如何降低影響?因為我們只是想在第一條里去插入一個東西,后面這些元素本身是不會受到影響的,形狀和大小都不會變,這個時候我們就用到contain,contain是開發者和瀏覽器進行溝通的一個屬性,通過contain:layout告訴瀏覽器,相當于你可以把它看成一個盒子,盒子里所有的子元素和盒子外面的元素之間沒有任何布局上的關系,也就是說里面無論我怎么變化不會影響外面,外面怎么變化也不會影響盒子里面,這樣瀏覽器就非常清楚了,盒子里面的元素如果有任何的變化,我會單獨的處理,不需要管理頁面上其他的部分,這樣我們就可以大大減少重新去進行回流或者布局時的計算,這就是contain:layout的作用 - 使用font-display屬性,可以幫助我們讓我們的文字更早的顯示在頁面上,同時可以適當減輕文字閃動的問題
總結
以上是生活随笔為你收集整理的(四)代码优化 (快来看看怎样写出真正高性能的代码)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 图文详解Ubuntu搭建Ftp服务器的方
- 下一篇: 图片资源优化