【讀書筆記】《高效能JavaScript》

OBKoro1發表於2018-01-16

缺陷

這本書是2010年出版的,這本書談效能是有時效性的,現在馬上就2018年了,這幾年前端發展的速度是飛快的,書裡面還有一些內容考慮IE6、7、8的東西,殊不知現在這些都已經不再考慮了,所以不可避免的有一些知識是比較老的。有些解決方法現在已經不是最好的解決方式,比如工具那一章。

前言

總的來說,這本書整體給出的效能優化建議,以及作者耐心的實踐,對我們開發優化的啟發和幫助還是很大的,因為它裡邊的很多知識,都是作者通過實踐總結出來的,都是經驗的積累,這在一般的教科書上是學不到的。特別是對於js基礎比較差一點的,裡面有很多知識點儘管在現在還是非常有必要的。

下面我就將各章節的一些重要的知識點總結寫出來,爭取將乾貨都提取出來

本文首發於我的個人blog:obkoro1.com


正文

第一章-載入和執行

  1. js的阻塞特性:

當瀏覽器在執行js程式碼的時候,不能同時做其他事情。(介面ui執行緒和js執行緒用的是同一程式,所以js執行越久,網頁的響應時間越長。)

  1. 指令碼的位置

如果把指令碼<script>放在<head>中,頁面會等js檔案全部下載並執行完成後才開始渲染,在這些檔案下載和執行的過程中:會導致訪問網站的時候有明顯的延遲,表現為:頁面空白。

效能提升:推薦將所有的<script>標籤儘可能的放到<body>標籤的底部,優先渲染頁面,減少頁面空白時間。

  1. 元件指令碼。

每個<script>標籤初始下載的時候都會阻塞頁面的渲染。效能提升做法: 減少內嵌指令碼:減少內嵌的<script>標籤,將程式碼寫在一個標籤中。

合併外鏈的js檔案:http請求會帶來額外的效能開銷,栗子:下載一個100KB的js檔案比下載4個25kb的js檔案更快。具體操作方法自行搜尋。

  1. 無阻塞指令碼的方法

script標籤的aync屬性

async 屬性規定一旦指令碼可用,則會非同步執行。async 屬性僅適用於外部指令碼(只有在使用 src 屬性時)。如果 async="async":指令碼相對於頁面的其餘部分非同步地執行(當頁面繼續進行解析時,指令碼將被執行)

script標籤的defer屬性

js檔案在頁面解析到script標籤的時候開始下載,但並不會執行,dom載入完成執行。這兩個屬性的區別在於執行時機

動態指令碼元素

js操作dom建立<script>標籤,自定義生成標籤的type、src屬性。檔案會在該元素被新增到頁面的時候開始下載。ps:如果檔案順序很重要的話,最好按照順序合成一個檔案。然後再新增到頁面中。這樣:無論何時啟動下載。檔案的下載和執行過程不會阻塞頁面的其他程式。

3. XHR ajax指令碼注入、

用get請求一個檔案,請求好了。然後建立動態指令碼,最後新增進去。 缺陷:檔案要再請求頁面的同一個域。不能從cdn下載

第一章載入和執行小結:

  1. 把檔案放在body標籤簽名,
  2. 合併指令碼,減少<script>標籤。
  3. 採用無阻塞下載js。使用script的defer和async屬性 非同步下載。動態建立script標籤和執行程式碼。

第二章-資料存取

  1. js四種基本的資料存取位置。 1、字面量:字串、數字、布林、物件、陣列、函式、正則、null、undefined,字面量只代表自身,沒有儲存位置。 2、區域性變數。 let var 宣告的變數。3、陣列元素。4、物件成員。

效能:訪問字面量和區域性變數的速度是最快的,訪問陣列和物件成員相對較慢

  1. 變數識別符號解析過程

搜尋執行環境的作用域鏈,查詢同名識別符號。搜尋過程從作用域鏈頭部開始,也就是當前執行函式的活動物件。如果找到,就使用這個識別符號,對應的變數;如果沒有找到,繼續搜尋下面的物件。搜尋過程會持續進行,直到找到識別符號,若無法搜尋到匹配的物件,那麼識別符號被視為未定義、

效能問題:一個識別符號所在的位置越深,它的讀寫速度也就越慢。因此,函式中讀寫區域性變數總是最快的,而讀寫全域性變數通常是最慢的。

建議:將全域性變數儲存到區域性變數,加快讀寫速度。

  1. 閉包,原型,巢狀物件。

優化建議:將常用的跨作用域變數儲存到區域性變數,然後直接訪問區域性變數。理由如上,變數識別符號解析過程。

第二章資料存取小結:

  1. 訪問字面量和區域性變數的速度最快,相反,訪問陣列元素和物件成員相對較慢。
  2. 由於區域性變數存在於作用域鏈的起始位置,因為訪問區域性變數比訪問跨作用域變數更快。這個道理同樣適用於陣列,物件,跨作用域變數
  3. 把常用的物件,陣列,跨域變數儲存在區域性變數可以改善js效能,區域性變數訪問速度更快。

第三章DOM程式設計小結:

  1. dom操作天生就慢,儘量減少dom操作,減少訪問dom的次數。
  2. 使用document.querySelect來做選擇器,比其他方式快。
  3. 需要多次訪問某個dom節點,使用區域性變數儲存。
  4. html集合,把集合長度快取到一個變數中,然後遍歷使用這個變數,如果經常操作集合,建議拷到一個陣列中。
  5. 要留意瀏覽器的重繪和重排;批量修改樣式的時候,‘離線’操作DOM樹,使用快取,並減少訪問佈局資訊的次數。 重繪和重排是DOM程式設計優化的一個相當重要概念:重繪和重排
  6. 動畫中使用絕對定義,使用拖放處理。
  7. 使用事件委託來減少事件處理器的數量。

第四章演算法和流程控制小結:

  1. for、while和do-while迴圈效能差不多,for-in迴圈速度只有前面幾種型別的1/7,所以儘量避免使用for-in迴圈,除非你需要遍歷一個屬性數量未知的物件。

    forEach比for慢,如果執行速度要求嚴格,不建議使用。

  2. 改善迴圈效能的最佳方式是減少每次迭代的工作量和減少迴圈迭代的次數

減少迭代工作量:減少屬性查詢和倒序迴圈,迴圈次數越多,效能優化越明顯。

    for(var i=0;i<items.length;i++){程式碼}//正序迴圈
    for(var i=items.length;i--){程式碼}//倒序迴圈
    //減少屬性查詢:查詢一次屬性,把值存在區域性變數,在控制條件裡面使用這個變數
    
    倒序迴圈在i>0的時候會自動轉換為true,等於0的時候為false。
    //倒序迴圈:控制條件從(迭代數少於總數嗎?它是否為true?)變為(它是否為true)
複製程式碼

減少迭代的次數:"Duff's Device"迴圈體展開技術,有興趣的可以看一下,迭代工作量大於1000的時候適用。

  1. if-else與switch:條件數量越大,越傾向於使用switch。

    優化if-else:

     1、把最可能出現的條件放在首位。2、使用二分法把值域分成一系列的區間。
    複製程式碼
  2. 瀏覽器的呼叫棧大小限制了遞迴演算法在js中的應用;棧溢位錯誤會導致其他程式碼中斷執行。

     小心使用遞迴,現在es6遞迴可以尾遞迴,在es6中只要使用尾遞迴就不會發生棧溢位,相對節省效能。
    複製程式碼

第五章字串和正規表示式小結:

  1. 字串合併的時候使用簡單的'+'和'+='操作符。

    str+='abc'+'efg;//2個以上的字串拼接,會產生臨時字串
    str=str+'abc'+'efg';//推薦,提速10%~40%  
    複製程式碼
  2. 書裡面講的正則原理和回溯原理,這個很重要,找個篇部落格:跟書裡面講的差不多,但還是建議大家可以去找找PDF好好看看正規表示式這節。

  3. 提高正規表示式效率的方法:

     1、最重要的是:具體化正規表示式!具體化正規表示式!具體化正規表示式!
     2、關注如何讓匹配更快失敗,找出一些必需,特殊的字元
     3、正規表示式以簡單的、必需的字元開始。
     4、使用量詞模式,使它們後面的字元互斥。
     5、較少分支數量,縮小分支範圍
     6、使用合適的量詞
     7、把正規表示式賦值給變數並重用
     8、將複雜的正則拆分為簡單的片段
     //事實上,書裡面講的方法不止這麼幾個,而且每一個都有詳細的解析 大佬們 還是去看看這一章節吧
    複製程式碼
  4. 正規表示式並不總是最好的解決方案,當你只是搜尋字面字串或者你事先知道字串的哪一部分將要被查詢時:

     使用indexOf()和lastIndexOf()更適合查詢特定字串的位置或者判斷它們是否存在
     //例如:判斷當前瀏覽器之類。
    複製程式碼

第六章快速響應的使用者介面小結:

js和使用者介面更新在同一個程式中執行,因此一次只能處理一件事情。高效的管理UI執行緒就是要確保js不能執行太長時間,以免影響使用者體驗。

  1. 瀏覽器限制了js任務的執行時間,這種限制很有必要,它確保某些惡意程式碼不能通過永不停止的密集操作鎖住使用者的瀏覽器。此限制分為兩種:呼叫棧的大小和長時間執行指令碼。

  2. 任何js任務都不應當執行超過100毫秒。過長的執行時間會導致UI更新出現明顯延遲,從而對使用者體驗造成負面影響。

  3. 定時器可用來安排程式碼延遲執行,它使得你可以把長時間執行指令碼分解成一系列的小任務。

第七章 AJAX 小結

這一章節貌似東西都比較老一點。。

  1. post更適合傳送大量資料到伺服器。

  2. get請求能夠被瀏覽器快取,Expires頭資訊設定瀏覽器快取請求多久。可用於從不改變的圖片或者其他靜態資料集(js、css等)

  3. JSON是一種使用js物件和陣列直接量編寫的輕量級且易於解析的資料格式,它不僅傳輸尺寸小,而且解析速度快。JSON是高效能AJAX的基礎,尤其在使用動態指令碼注入時。

json應該是近幾年一直在用的。。。

  1. 減少請求數,通過合併js和css檔案。
  2. 縮短頁面的載入時間,頁面主要內容載入完成後,用AJAX獲取那些次要的檔案。

第八章程式設計實踐小結

  1. 避免雙重求值:避免使用eval()和 function()構造器來避免雙重求值帶來的效能消耗,同樣的,給setTimeout()和setInterval()傳遞函式而不是字串作為引數。

     //雙重求值:就是在js程式碼中執行另一段js程式碼,不建議使用下面這些方式
     eval('程式碼') 
     function建構函式--new function('程式碼')
     setTimeout(‘程式碼’,100)和setInterval(‘程式碼’,100) 
    複製程式碼
  2. 儘量使用直接量建立物件和陣列。直接量的建立和初始化都比非直接量形式要快。

  3. 避免做重複工作,能節省的步驟就節省。

  4. js原生方法總會比你寫的任何程式碼都要快。

第九章 構建並部署高效能js應用小結

構建和部署的過程對基於js的web應用的效能有著巨大影響。這個過程中最重要的步驟有:

  1. 合併、壓縮js檔案。可使用Gzip壓縮,能夠減少約70%的體積

這些都是在構建過程中完成的工作,不要等到執行時去做,webpack也是在構建過程中,完成的工作。 2. 通過正確設定HTTP響應頭來快取js檔案,通過向檔名增加時間戳來避免快取問題。 3. 使用CDN提供js檔案;CDN不僅可以提升效能,它也為你管理檔案的壓縮與快取,。

第十章 工具 小結:

當網頁變慢時,分析從網路下載的資源以及分析的資源以及分析指令碼的執行效能能讓你專注於那些最需要優化的地方。

  1. 使用網路分析工具找出載入指令碼和頁面中其他資源的瓶頸,這會幫助你決定那些指令碼需要延遲載入,或者需要進一步分析。

     檢查圖片、樣式表和指令碼的載入過程,以及它們對頁面整體載入和渲染的影響。從而針對性的做出優化
    複製程式碼
  2. 把指令碼儘可能延遲載入,這樣做可以加快頁面渲染速度,給使用者帶來更好的體驗。

  3. 確認指令碼和其他資原始檔的載入過程已經被優化

     這裡主要是說檔案從伺服器的下載速度,比如伺服器那邊的配置問題之類的。
     栗子:我就被後端坑過。一個js檔案就200k ,下載下來需要50秒鐘!
     後面發現原來是後端那邊nginx沒有開啟加速配置什麼的,導致出現的問題,找問題找半天。 
    複製程式碼
  4. 測試指令碼的執行時間,用一個Date例項減去另一個Date例項,得到的時間差就是指令碼執行消耗的時間。

     let start=new Date();
     //你的程式碼
     let time=newDate()-start;
    複製程式碼
  5. chrome ,火狐 等主流瀏覽器的控制皮膚,已經能夠反映很多效能問題。仔細分析就能找出很多問題。例如:資源載入,斷點等

後話

事實上,自認為這本書最寶貴的就是一些提到的細節,比如:

1、字串合併的時候使用簡單的'+'和'+='操作符。

    str+='abc'+'efg;//2個以上的字串拼接,會產生臨時字串
    str=str+'abc'+'efg';//推薦,提速10%~40%
複製程式碼

2、避免雙重求值:避免使用eval()和 function()構造器來避免雙重求值帶來的效能消耗,同樣的,給setTimeout()和setInterval()傳遞函式而不是字串作為引數。

    //雙重求值:就是在js程式碼中執行另一段js程式碼,不建議使用下面這些方式
    eval('程式碼') 
    function建構函式--new function('程式碼')
    setTimeout(‘程式碼’,100)和setInterval(‘程式碼’,100) 
複製程式碼

這些東西可以讓我們知道什麼是更好的實踐,什麼樣的程式碼可以跑得更快,讓我們養成更好的開發習慣。

書不太厚,如果對裡面的內容感興趣,還是建議買一本回家看一看。

以上2018.1.9

相關文章