精讀《高效 javascript》

程式設計師解決師發表於2018-08-23

前言

本期我來給大家推薦的書是《高效能JavaScript》,在這本書中我們能夠了解 javascript 開發過程中的效能瓶頸,如何提升各方面的效能,包括程式碼的載入、執行、DOM互動、頁面生存週期等。同樣我們今天還是用思維導圖的方式來精讀一遍。(思維導圖圖片可能有點小,記得點開看,你會有所收穫)

載入和執行

精讀《高效 javascript》
管理瀏覽器中的 JavaScript 程式碼是個棘手的問題,因為程式碼執行阻塞了其他瀏覽器處理過程,諸如使用者 介面繪製。每次遇到<script>標籤,頁面必須停下來等待程式碼下載(如果是外部的)並執行,然後再繼續處 理頁面其他部分。但是,有幾種方法可以減少 JavaScript 對效能的影響:

  1. 將所有<script>標籤放置在頁面的底部,緊靠 body 關閉標籤</body>的上方。此法可以保證頁面在指令碼 執行之前完成解析。
  2. 將指令碼成組打包。頁面的<script>標籤越少,頁面的載入速度就越快,響應也更加迅速。不論外部指令碼 檔案還是內聯程式碼都是如此。

有幾種方法可以使用非阻塞方式下載 JavaScript:

  • <script>標籤新增 defer 屬性(只適用於 Internet Explorer 和 Firefox 3.5 以上版本)
  • 動態建立<script>元素,用它下載並執行程式碼
  • 用 XHR 物件下載程式碼,並注入到頁面中

通過使用上述策略,你可以極大提高那些大量使用 JavaScript 程式碼的網頁應用的實際效能。

資料存取

精讀《高效 javascript》

在 JavaScript 中,資料儲存位置可以對程式碼整體效能產生重要影響。有四種資料訪問型別:直接量,變 量,陣列項,物件成員。它們有不同的效能考慮。

直接量和區域性變數訪問速度非常快,陣列項和物件成員需要更長時間。區域性變數比域外變數快,因為它位於作用域鏈的第一個物件中。變數在作用域鏈中的位置越深,訪問所需 的時間就越長。全域性變數總是最慢的,因為它們總是位於作用域鏈的最後一環。避免使用 with 表示式,因為它改變了執行期上下文的作用域鏈。而且應當小心對待 try-catch 表示式的 catch 子句,因為它具有同樣效果。巢狀物件成員會造成重大效能影響,儘量少用。

一個屬性或方法在原形鏈中的位置越深,訪問它的速度就越慢。一般來說,你可以通過這種方法提高 JavaScript 程式碼的效能:將經常使用的物件成員,陣列項,和域外變 量存入區域性變數中。然後,訪問區域性變數的速度會快於那些原始變數。通過使用這些策略,你可以極大地提高那些需要大量 JavaScript 程式碼的網頁應用的實際效能。

DOM 程式設計

精讀《高效 javascript》
DOM 訪問和操作是現代網頁應用中很重要的一部分。但每次你通過橋樑從 ECMAScript 島到達 DOM 島 時,都會被收取“過橋費”。為減少 DOM 程式設計中的效能損失,請牢記以下幾點:

最小化 DOM 訪問,在 JavaScript 端做盡可能多的事情。在反覆訪問的地方使用區域性變數存放 DOM 引用.小心地處理 HTML 集合,因為他們表現出“存在性”,總是對底層文件重新查詢。將集合的 length 屬性緩 存到一個變數中,在迭代中使用這個變數。如果經常操作這個集合,可以將集合拷貝到陣列中。

如果可能的話,使用速度更快的 API,諸如 querySelectorAll()和 firstElementChild。注意重繪和重排版;批量修改風格,離線操作 DOM 樹,快取並減少對佈局資訊的訪問。動畫中使用絕對座標,使用拖放代理。使用事件託管技術最小化事件控制程式碼數量。

演算法和流程控制

精讀《高效 javascript》
正如其他程式語言,程式碼的寫法和演算法選用影響 JavaScript 的執行時間。與其他程式語言不同的是, JavaScript 可用資源有限,所以優化技術更為重要。

for,while,do-while 迴圈的效能特性相似,誰也不比誰更快或更慢。除非你要迭代遍歷一個屬性未知的物件,否則不要使用 for-in 迴圈。改善迴圈效能的最好辦法是減少每次迭代中的運算量,並減少迴圈迭代次數。

一般來說,switch 總是比 if-else 更快,但並不總是最好的解決方法。當判斷條件較多時,查表法比 if-else 或者 switch 更快。

瀏覽器的呼叫棧尺寸限制了遞迴演算法在 JavaScript 中的應用;棧溢位錯誤導致其他程式碼也不能正常執行。如果你遇到一個棧溢位錯誤,將方法修改為一個迭代演算法或者使用製表法可以避免重複工作。

執行的程式碼總量越大,使用這些策略所帶來的效能提升就越明顯。

字串和正規表示式

精讀《高效 javascript》
密集的字串操作和粗淺地編寫正規表示式可能是主要效能障礙,但本章中的建議可幫助您避免常見缺陷。當連線數量巨大或尺寸巨大的字串時,陣列聯合是 IE7 和它的早期版本上唯一具有合理效能的方法。如果你不關心 IE7 和它的早期版本,陣列聯合是連線字串最慢的方法之一。使用簡單的+和+=取而代之, 可避免(產生)不必要的中間字串。

回溯既是正規表示式匹配功能基本的組成部分,又是正規表示式影響效率的常見原因。回溯失控發生在正規表示式本應很快發現匹配的地方,因為某些特殊的匹配字串動作,導致執行緩慢 甚至瀏覽器崩潰。避免此問題的技術包括:使相鄰字元互斥,避免巢狀量詞對一個字串的相同部分多次 匹配,通過重複利用前瞻操作的原子特性去除不必要的回溯。

提高正規表示式效率的各種技術手段,幫助正規表示式更快地找到匹配,以及在非匹配位置上花費更少 時間(見《更多提高正規表示式效率的方法》)。正規表示式並不總是完成工作的最佳工具,尤其當你只是搜尋一個文字字串時。

雖然有很多方法來修整一個字串,使用兩個簡單的正規表示式(一個用於去除頭部空格,另一個用於 去除尾部空格)提供了一個簡潔、跨瀏覽器的方法,適用於不同內容和長度的字串。從字串末尾開始 迴圈查詢第一個非空格字元,或者在一個混合應用中將此技術與正規表示式結合起來,提供了一個很好的 替代方案,它很少受到字串整體長度的影響。

快速響應使用者介面

精讀《高效 javascript》
JavaScript 和使用者介面更新在同一個程式內執行,同一時刻只有其中一個可以執行。這意味著當 JavaScript 程式碼正在執行時,使用者介面不能響應輸入,反之亦然。有效地管理 UI 執行緒就是要確保 JavaScript 不能執行 太長時間,以免影響使用者體驗。最後,請牢記如下幾點:

  1. JavaScript 執行時間不應該超過 100 毫秒。過長的執行時間導致 UI 更新出現可察覺的延遲,從而對整體 使用者體驗產生負面影響。
  2. JavaScript 執行期間,瀏覽器響應使用者互動的行為存在差異。無論如何,JavaScript 長時間執行將導致用 戶體驗混亂和脫節。
  3. 定時器可用於安排程式碼推遲執行,它使得你可以將長執行指令碼分解成一系列較小的任務。

網頁工人執行緒是新式瀏覽器才支援的特性,它允許你在 UI 執行緒之外執行 JavaScript 程式碼而避免鎖定 UI。網頁應用程式越複雜,積極主動地管理 UI 執行緒就越顯得重要。沒有什麼 JavaScript 程式碼可以重要到允 許影響使用者體驗的程度。

Ajax

精讀《高效 javascript》
高效能 Ajax 包括:知道你專案的具體需求,選擇正確的資料格式和與之相配的傳輸技術。

作為資料格式,純文字和 HTML 是高度限制的,但它們可節省客戶端的 CPU 週期。XML 被廣泛應用 普遍支援,但它非常冗長且解析緩慢。JSON 是輕量級的,解析迅速(作為原生程式碼而不是字串),交 互性與 XML 相當。字元分隔的自定義格式非常輕量,在大量資料集解析時速度最快,但需要編寫額外的 程式在伺服器端構造格式,並在客戶端解析。

當從頁面域請求資料時,XHR 提供最完善的控制和靈活性,儘管它將所有傳入資料視為一個字串, 這有可能降低解析速度。另一方面,動態指令碼標籤插入技術允許跨域請求和本地執行 JavaScript 和 JSON, 雖然它的介面不夠安全,而且不能讀取資訊頭或響應報文程式碼。多部分 XHR 可減少請求的數量,可在一次響應中處理不同的檔案型別,儘管它不能快取收到的響應報文。當傳送資料時,影象燈標是最簡單和最 有效的方法。XHR 也可用 POST 方法傳送大量資料。

除這些格式和傳輸技術之外,還有一些準則有助於進一步提高 Ajax 的速度:

  1. 減少請求數量,可通過 JavaScript 和 CSS 檔案打包,或者使用 MXHR。
  2. 縮短頁面的載入時間,在頁面其它內容載入之後,使用 Ajax 獲取少量重要檔案。
  3. 確保程式碼錯誤不要直接顯示給使用者,並在伺服器端處理錯誤。
  4. 學會何時使用一個健壯的 Ajax 庫,何時編寫自己的底層 Ajax 程式碼。

Ajax 是提升你網站潛在效能之最大的改進區域之一,因為很多網站大量使用非同步請求,又因為它提供 了許多不相關問題的解決方案,這些問題諸如,需要載入太多資源。對 XHR 的創造性應用是如此的與眾 不同,它不是呆滯不友好的介面,而是響應迅速且高效的代名詞;它不會引起使用者的憎恨,誰見了它都會 愛上它。

程式設計實踐

精讀《高效 javascript》

JavaScript 提出了一些獨特的效能挑戰,關係到你組織程式碼的方法。網頁應用變得越來越高階,包含的 JavaScript 程式碼越來越多,出現了一些模式和反模式。請牢記以下程式設計經驗:

  1. 通過避免使用 eval_r()和 Function()構造器避免二次評估。此外,給 setTimeout()和 setInterval()傳遞函式參 數而不是字串引數。
  2. 建立新物件和陣列時使用物件直接量和陣列直接量。它們比非直接量形式建立和初始化更快。
  3. 避免重複進行相同工作。當需要檢測瀏覽器時,使用延遲載入或條件預載入。
  4. 當執行數學遠算時,考慮使用位操作,它直接在數字底層進行操作。
  5. 原生方法總是比 JavaScript 寫的東西要快。儘量使用原生方法。

構建並部署高效能 javascript 應用

開發和部署過程對基於 JavaScript 的應用程式可以產生巨大影響,最重要的幾個步驟如下:

  1. 合併 JavaScript 檔案,減少 HTTP 請求的數量
  2. 使用 YUI 壓縮器緊湊處理 JavaScript 檔案
  3. 以壓縮形式提供 JavaScript 檔案(gzip 編碼)
  4. 通過設定 HTTP 響應報文頭使 JavaScript 檔案可快取,通過向檔名附加時間戳解決快取問題
  5. 使用內容傳遞網路(CDN)提供 JavaScript 檔案,CDN 不僅可以提高效能,它還可以為你管理壓縮和緩 存

所有這些步驟應當自動完成,不論是使用公開的開發工具諸如 Apache Ant,還是使用自定義的開發工具 以實現特定需求。如果你使這些開發工具為你服務,你可以極大改善那些大量使用 JavaScript 程式碼的網頁 應用或網站的效能。

工具

當網頁或應用程式變慢時,分析網上傳來的資源,分析指令碼的執行效能,使你能夠集中精力在那些需要 努力優化的地方。使用網路分析器找出載入指令碼和其它頁面資源的瓶頸所在,這有助於決定哪些指令碼需要延遲載入,或者 進行進一步分析。傳統的智慧告訴我們應儘量減少 HTTP 請求的數量,儘量延遲載入指令碼以使頁面渲染速度更快,向使用者 提供更好的整體體驗。使用效能分析器找出指令碼執行時速度慢的部分,檢查每個函式所花費的時間,以及函式被呼叫的次數, 通過呼叫棧自身提供的一些線索來找出哪些地方應當努力優化。雖然花費時間和呼叫次數通常是資料中最有價值的點,還是應當仔細察看函式的呼叫過程,可能發現其 它優化方法。這些工具在那些現代程式碼所要執行的程式設計環境中不再神祕。在開始優化工作之前使用它們,確保開發時 間用在解決問題的刀刃上。

?往期的讀書筆記 && 技術文章

為了系統的串聯前端知識,我平時喜歡用思維導圖來記錄讀書筆記,我在 github 建了倉庫放這些思維導圖的原件,和讀書筆記。如果你也喜歡用思維導圖的方式來記錄讀書筆記,也歡迎和我一同維護這個倉庫,歡迎留言或則微信(646321933)與我交流

精讀《你不知道的 javascript(上卷)》

精讀《你不知道的javascript》中卷

精讀《深入淺出Node.js》

精讀《圖解HTTP》

javascript 垃圾回收演算法

你需要知道的單頁面路由實現原理

javascript 實現高仿 growingIO

新鮮出爐的8月前端面試題

思維導圖下載地址

相關文章