從 JavaScript 著手優化渲染效能
19 Jul 2018JavaScript 只是開發者撰寫的程式碼,而非實際執行的程式碼,這聽起來很奇怪,但其實是這樣的…現今 JavaScript 引擎會編譯開發者所撰寫的程式碼,優化而使其效能更好後,再到瀏覽器執行,而這個編譯引擎就是 JIT(Just in Time Compiler)。
圖片來源:Browser Rendering – Performance past the first page load
避免 Micro-optimization
有些人在嘗試撰寫效能佳的程式碼時,會思考這個問題「迴圈用 for 還是 while 比較好呢?」但我們並不知道 JavaScript 引擎如何看待這兩者,或說如何優化,所以有這種比較相似程式碼片段的焦慮是不必要的。
然而,若想改善效能,是可以從其他方面著手的,像是 Critical Rendering Path、requestAnimationFrame、使用工具 JavaScript Profiler 檢視瓶頸所在、使用 Web Workers 執行複雜運算、控管記憶體等。
requestAnimationFrame
使用 requestAnimationFrame
,瀏覽器就能依照自身狀況優化動畫效能,而不像 setTimeout
或 setInterval
可能中斷瀏覽器正在進行的工作,無法顧及 Browser Rendering Pipeline 到哪一個階段了,而導致畫面渲染時遺失了某些 Frame,在使用者看來就是發生顫動(Juddering)。
圖片來源:Making a Silky Smooth Web
範例。
function animate() {
// do something...
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
現今瀏覽器只有 IE9 不支援 requestAnimationFrame,但沒關係,使用 polyfill 來補足即可。
JavaScript Profiler
Chrome Devtools 的 JavaScript Profiler 可檢視 JavaScript 程式碼的 function call stack 和花了多少時間來執行,或也可以使用 Performance 的 Timeline。JavaScript Profiler 位於 More tools > JavaScript Profiler。
Web Workers
關於 Web Workers 另外整理了一篇文章,可參考這裡。
JS Memory Management
JavaScript 引擎會自動回收記憶體(Garbage Collection),但可能會造成某些 Frame 的遺失,而產生顫動(Juddering)。
若希望有效進行垃圾回收,以下有一些規則
- 不要使用全域變數,儘量使用區域變數(註一)。
- 在 DOM Element 即將被移除前或不再需要監聽事件時,取消綁定事件監聽。
- 減少 DOM Element 的操作、使用 DocumentFragment 來加速操作 DOM Element,點此看範例;或縮小 Reflow 範圍,點此看說明。
- 適時清除暫存資料或有效更新,避免儲存大量用不到的資料。
如何觀察記憶體回收的情況?從 Chrome Devtools 的 Performace 頁籤打開 Memory 選項,即可看到 Memory Timeline。
這裡我們需要注意
- JS Heap Size 歸零,代表執行 Garbage Collection。
- 若執行記憶體回收後沒有歸零,表示有 Memory Leak。
若想觀察呼叫記憶體回收的時機和細節,可取消勾選 Memory,並在 Timeline 搜尋 GC。
若因為記憶體回收而顫動過於頻繁,那就要試圖找出原因,像是…某個 function 產生過多的垃圾,使得 JavaScript 引擎過度做記憶體回收。
More on Garbage Collection
- Writing Fast, Memory-Efficient JavaScript on Smashing Magazine
- Memory Management on MDN
- High-Performance, Garbage-Collector-Friendly Code on Build New Games
備註
- 註一:只要執行的網頁是打開的,瀏覽器就不會回收這個頁面的全域變數(Global Varialbe)。例如:使用 var 宣告的變數或 window 的 property。因此,只有在重新整理、跳轉去其他頁面、關閉 tab 或退出瀏覽器的時候才會做回收全域變數的動作。當記憶體不足時,首先回收 function 內的變數,像是那些不再用到的 function-若希望記憶體控管得當,多使用區域變數,像是 ES6 的 let 和 const 來取代 var。