1.加載與執行:javascript
(1)將腳本放在底部;(不然會阻塞)css
(2)因爲每一個<script>標籤初始下載時都會阻塞頁面渲染,因此減小頁面包含的<script>標籤數量有助於改善這一狀況。這不只僅針對外鏈腳本,內嵌腳本的數量一樣也要限制。瀏覽器在解析HTML頁面的過程當中每遇到一個<script>標籤,都會因執行腳本而致使必定的延時,所以最小化延遲時間將會明顯改善頁面的整體性能。(多個js文件可使用合併處理器,合成一個js文件)java
(3)延遲的腳本:defer屬性;正則表達式
任何帶有defer屬性的<script>元素在DOM完成加載以前都不會被執行,不管內嵌或外鏈腳本都是如此。帶有defer屬性的<script>元素不是跟在第二個後面執行,而是在onload事件處理器執行以前被調用。算法
(4)推薦的無阻塞模式:向頁面添加大量javascript的推薦作法只有兩步:先添加動態加載所需的代碼,而後加載初始化頁面所需的剩下的代碼。(封裝的loadScript()函數加載)chrome
(5)XHR腳本注入:這種方法的主要優勢是,你能夠下載不當即執行的 JavaScript 代碼。因爲代碼返回在<script>標籤以外 (換句話說不受<script>標籤約束),它下載後不會自動執行,這使得你能夠推遲執行,直到一切都準備好了。另外一個優勢是,一樣的代碼在全部現代瀏覽器中都不會引起異常。 最主要的限制是: JavaScript 文件必須與頁面放置在同一個域內,不能從 CDNs 下載( CDN 指「內容投遞網絡( Content Delivery Network) 」,前面 002 篇《成組腳本》一節提到)。正由於這個緣由,大型網頁一般不採用 XHR 腳本注入技術。 編程
總結:多種無阻塞下載js文件的方法:a. 使用<script>標籤的defer屬性; b. 使用動態建立的<script>元素來下載並執行代碼; c. 使用XHR對象下載js代碼並注入頁面;數組
2.數據存取:瀏覽器
(1)javascript有四種基本的數據存取位置:字面量;本地變量;數組元素;對象成員;緩存
字面量:只表明自身,不存儲在特別位置。js中的字面量有:字符串、數字、布爾值、對象、數組、函數。正則表達式、null及undefined值;
本地變量:開發人員使用關鍵字var定義的數據存儲單元;
數組元素:存儲在js數組對象內部,以數字做爲索引;
對象成員:存儲在js對象內部,以字符串做爲索引;
若是在意運行速度,那麼儘可能使用字面量和局部變量,減小數組項和對象成員的使用。
(2)做用域:
每個 JavaScript 函數都被表示爲對象。進一步說,它是一個函數實例。函數對象正如其餘對象那樣,擁有你能夠編程訪問的屬性,和一系列不能被程序訪問,僅供 JavaScript 引擎使用的內部屬性。其中一個內部屬性是[[Scope]]。內部[[Scope]]屬性包含一個函數被建立的做用域中對象的集合。此集合被稱爲函數的做用域鏈,它決定哪些數據可由函數訪問。此函數做用域鏈中的每一個對象被稱爲一個可變對象,每一個可變對象都以「鍵值對」 的形式存在。當一個函數建立後,它的做用域鏈被填充以對象,這些對象表明建立此函數的環境中可訪問的數據。
function add(num1, num2){ var sum = num1 + num2; return sum; }
(3)標識符解析的性能:在執行環境的做用域鏈中,一個標識符所在的位置越深,它的讀寫速度也就越慢。所以,函數中讀寫局部變量老是最快的,而讀寫全局變量一般是最慢的(優化js引擎在某些狀況下能有所改善,好比chrome和safari)。請記住,全局變量老是存在於執行環境做用域鏈的最末端,所以它也是最遠的。
所以,在沒有優化js引擎的瀏覽器中,建議儘量使用局部變量,一個好的經驗法則是:若是某個跨做用域的值在函數中被引用一次以上,那麼就把它存儲到局部變量裏。
好比: function initUI(){ var bd = document.body, links = document.getElementsByTagName_r("a"), i = 0, len = links.length; while(i < len){ update(links[i++]); } document.getElementById("go-btn").onclick = function(){ start(); }; bd.className = "active"; }
可重寫爲:
function initUI(){ var doc = document, bd = doc.body, links = doc.getElementsByTagName_r("a"),i = 0, len = links.length; while(i < len){ update(links[i++]); } doc.getElementById("go-btn").onclick = function(){ start(); }; bd.className = "active"; }
更新後的 initUI() 函數首先把 document 對象的引用存儲到局部變量 doc 中,訪問局部變量的次數由3次減小到1次。因爲doc是局部變量,所以經過它訪問 document 會更快。
(4)對象成員:
搜索實例成員比從字面量或局部變量中讀取數據代價更高,再加上遍歷原型鏈帶來的開銷,這讓性能問題更爲嚴重。
因爲全部這些性能問題與對象成員有關,因此若是可能的話請避免使用它們。更確切地說,你應當當心地,只在必要狀況下使用對象成員。例如,沒有理由在一個函數中屢次讀取同一個對象成員的值 。通常來講,若是在同一個函數中你要屢次讀取同一個對象屬性,最好將它存入一個局部變量。以局部變量替代屬性,避免多餘的屬性查找帶來性能開銷。
(提示:這種優化並不推薦用於對象的成員方法。由於許多對象方法使用this來判斷執行環境,把一個對象方法保存在局部變量會致使this綁定到window,而this值的改變會使得js引擎沒法正確解析它的對象成員,進而致使程序出錯)
總結:
1. 在 JavaScript 中,數據存儲位置能夠對代碼總體性能產生重要影響。有四種數據訪問類型:直接量,變量,數組項,對象成員。它們有不一樣的性能考慮。 直接量和局部變量訪問速度很是快,數組項和對象成員須要更長時間。
2. 局部變量比域外變量快,由於它位於做用域鏈的第一個對象中。變量在做用域鏈中的位置越深,訪問所需的時間就越長。全局變量老是最慢的,由於它們老是位於做用域鏈的最後一環。
3. 避免使用 with 表達式, 由於它改變了運行期上下文的做用域鏈。並且應當當心對待 try-catch 表達式的 catch子句,由於它具備一樣效果。
4. 嵌套對象成員會形成重大性能影響,儘可能少用
5. 一個屬性或方法在原形鏈中的位置越深,訪問它的速度就越慢。
通常來講,你能夠經過這種方法提升 JavaScript 代碼的性能:將常用的對象成員,數組項,和域外變量存入局部變量中。而後,訪問局部變量的速度會快於那些原始變量。
3. DOM編程:
1. 瀏覽器中一般會把DOM和javascript獨立實現;簡單理解,即兩個相互獨立的功能只要經過接口彼此鏈接,就會產生消耗;
2. 訪問DOM元素是有代價的,而修改元素則更甚,它會致使瀏覽器從新計算計算頁面的幾何變化;——— 減小訪問DOM的次數,把運算儘可能留在 ECMAScript 這一端處理;
3. 修改頁面區域的最佳方案是用非標準但支持良好的 innerHTML 屬性仍是 document.createElement() 原生DOM方法:在除開最新版的 Webkit 內核(Chrome 和 Safari)以外的全部瀏覽器中, innerHTML 會更快一點;
4. 使用 DOM 方法更新頁面內容的另外一個途徑是克隆已有元素,即便用 element.cloneNode() (element表示已有節點)替代 document.createElement();且在大多數瀏覽器中,節點克隆都更有效率;
5. HTML 集合是包含了 DOM 節點引用的類數組對象。好比:document.getElementByName()、document.getElementByTagName、document.images 等,返回值均爲類數組對象;事實上,HTML 集合一直與文檔保持着鏈接,每次你須要最新的信息時,都會重複執行查詢的過程,哪怕只是獲取集合裏的元素個數(即訪問集合的 length 屬性)也是如此,這正是低效之源;
6. 遍歷數組比遍歷集合快,所以若是先將集合元素拷貝到數組中,訪問它的屬性會更快;
如下的 toArray() 函數可做爲一個通用的集合轉數組函數:
function toArray(){ for (var i=0, a=[], len=coll.length; i<len; i++) { a[i] =coll[i]; } return a; }
7. nextSibling 與 childNodes:在 IE 中,nextSibling 方法查找 DOM 節點更快,其他 nextSibling 與 childNodes 差別不大;
8. 如下爲瀏覽器提供的一些API,可只返回元素節點:
9. 選擇器 API:
最新瀏覽器提供了 querySelectorAll() 方法,該方法不會返回 HTML 集合,而是返回一個 NodeList(包含着匹配節點的類數組對象),所以返回的節點不會對應實時的文檔結構,同時也就避免了 HTML 集合引發的性能(和潛在邏輯)問題;
10. 重繪和重排:
瀏覽器下載完頁面中的全部組件——HTML標記、Javascript、CSS、圖片——以後會解析並生成兩個內部數據結構:DOM 樹(表示頁面結構)和渲染樹(表示DOM 節點如何顯示);
當頁面佈局和集合屬性改變時就須要「重排」,如:增刪可見的 DOM 元素、元素位置改變、元素尺寸改變、內容改變、頁面渲染器初始化、瀏覽器窗口尺寸改變;而有些改變會觸發整個頁面的重排,如:當滾動條出現時;
因爲每次重排都產生計算消耗,大多數瀏覽器經過隊列化修改並批量執行來優化重排過程,但可能會無心間強制刷新隊列並要求計劃任務馬上執行:獲取佈局信息的操做會致使列隊刷新,好比:
以上屬性和方法須要返回最新的佈局信息,所以瀏覽器不得不執行渲染列隊中的「待處理變化」並觸發重排以返回正確的值;
在修改樣式的過程當中,最好避免使用上面列出的屬性,它們都會刷新渲染隊列,即便你是在獲取最近未發生改變的或者與最新改變無關的佈局信息;
最小化重繪重排:合併屢次對 DOM 和樣式的修改,而後一次處理掉;
(1)使用 cssText 屬性
(2)修改 CSS 的 class 名稱(該方法適用於不依賴於運行邏輯和計算的狀況)
批量修改DOM: 使元素脫離文檔流——對其應用多重改變——把元素帶回文檔中(基本思路);
(1)經過改變 display 屬性,臨時從文檔中移除,而後再恢復它;
(2)在文檔以外建立並更新一個文檔片段,而後把它附加到原始列表中。文檔片段是個輕量級的 document 對象,它的設計初衷就是爲了完成這類任務——更新和移動節點。文檔片段的一個便利的語法特性是當你附加一個片段到節點中時,實際上被添加的是該片段的子節點,而不是片段自己。
(3)爲須要修改的節點建立一個備份,而後對副本進行操做,一旦操做完成,就用新的節點替代舊的節點;
推薦使用文檔碎片;
緩存佈局信息:瀏覽器嘗試經過隊列化修改和批量執行的方式最小化重排次數。當查詢佈局信息時,如獲取偏移量(offsets)、滾動位置(scroll values)或計算出的樣式值(computedstyle values)時,瀏覽器爲了返回最新值,會刷新隊列並應用全部變動。最好的作法是儘可能減小布局信息的獲取次數,獲取後把它賦值給局部變量,而後再操做局部變量;
讓元素脫離文檔流:
使用如下步驟能夠避免頁面中的大部分重排:
1. 使用絕對位置定位頁面上的動畫元素,將其脫離文檔流;
2. 讓元素動起來。當它擴大時,會臨時覆蓋部分頁面。但這只是頁面一個小區域的重繪過程,不會產生重排並重繪頁面的大部份內容。
3. 當動畫結束時恢復定位,從而只會下移一次文檔的其餘元素。
IE中大量使用:hover該css僞選擇器會下降性能,避免表格或很長的列表中使用;
總結:
1. 最小化 DOM 訪問次數,儘量在Javascript 端處理;
2. 若是須要屢次訪問某個 DOM 節點,請使用局部變量存儲它的引用;
3. 當心處理 HTML 集合,由於它實時連繫着底層文檔。把集合的長度緩存到一個變量中,並在迭代中使用它。若是須要常常操做集合,建議把它拷貝到一個數組中;
4. 若是可能的話,使用速度更快的 API,好比 querySelectorAll() 和 firstElementChild;
5. 留意重繪(repaint)和重排(reflow),批量修改樣式時,「離線」操做 DOM 樹,使用緩存,並減小訪問佈局信息的次數;
6. 動畫中使用絕對定位,使用拖放代理(脫離文檔流——動畫展現——迴歸文檔流——只會下移一次文檔中其餘元素);
7. 使用事件委託(子委託父)來減小事件處理器的數量;
4. 算法和流程控制:
(1)循環:四種循環方式:for、while、do...while、for in;其中for in的速度是最慢的,其餘三個差很少;或使用如下方法:
var props = ["prop1", "prop2"], i = 0; while (i < props.length) { process(object[props[i++]]); }
提高循環性能,兩個主要可選因素:每次迭代處理的事務;迭代的次數;
經過減小這二者中的一個或者所有的時間開銷,就能提高循環總體性能;
(2)循環性能:倒序循環能夠略微提高性能(當排除額外操做帶來的影響時):減小了與條件比較這一步,直接將數值到0時是否爲false進行條件判斷;
當循環複雜度爲O(n)時,減小每次迭代的工做量是最有效的方法;當複雜度大於O(n),建議着重減小迭代次數;
(3)減小迭代次數:廣爲人知的一種限制循環迭代次數的模式:「達夫設備(Duff's Device)」;「達夫設備」的基本理念是:每次循環最多可調用8次 process()。循環的迭代次數爲總數除以8。因爲不是全部數字都能被8整除,變量 startAt 用來存放餘數,表示第一次循環中應調用多少次 process()。若是是12次,那麼第一次循環會調用 process() 4次,第二次循環調用 process() 8次,用兩次循環替代了12次循環。
原始版:
進化版:
(4)基於函數的迭代:原生數組方法 forEach();方便,但仍比基於循環的迭代要慢一些,而對每一個數組項調用外部方法所帶來的開銷是速度慢的主要緣由;
(5)條件語句:
優化 if-else 的目標:最小化到達正確分支前所需判斷的條件數量。
最簡單方法:確保最可能出現的條件放在首位;或使用二分法把值域分紅一系列的區間,而後逐步縮小範圍。二分法很是適用於有多個值域須要測試的時候(若是是離散值,switch語句更合適)。
(6)查找表:js可用使用數組和普通對象來構建查找表,經過查找表訪問數據比用 if-else 或 switch 快不少,特別是在條件語句數量很大的時候;
(7)遞歸:
遞歸函數的潛在問題是終止條件不明確或缺乏終止條件會致使函數長時間運行,並使得用戶界面處於假死狀態。並且,遞歸函數還可能遇到瀏覽器的「調用棧大小限制」(call stack size limites)。
兩種遞歸模式:函數調用自身;兩個函數相互調用;
function recurse(){ recurse(); } recurse();
function first(){ second(); } function second(){ first(); } first();
定位模式錯誤的第一步是驗證終止條件。
不過建議迭代、Memoization,或者綜合二者使用。
(8)迭代: 運行一個循環比反覆調用一個函數的開銷要少得多。
把遞歸算法改成迭代實現是避免棧溢出錯誤的方法之一。
如下爲一個遞歸用迭代實現的例子:
遞歸:
迭代:
(9)Memoization:它緩存前一個計算結果供後續計算使用,避免了重複工做。具體以下圖:
總結:
1. for、while 和 do-while 循環性能特性至關,並無一種循環類型明顯快於或慢於其餘類型;
2. 避免使用 for-in 循環,除非你須要遍歷一個屬性數量未知的對象;
3. 改善循環性能的最佳方式是減小每次迭代的運算量和減小循環迭代次數;
4. 一般來講,swich 老是比 if-else 快,但並不老是最佳解決方案;
5. 在判斷條件較多時,使用查找表比 if-else 和 switch 更快;
6. 瀏覽器的調用棧大小限制了遞歸算法在 Javascript 中的應用;棧溢出錯誤會致使其餘代碼中斷運行;
7. 若是你遇到棧溢出錯誤,可將方法改成迭代算法,或使用 Memoization 來避免重複計算;
運行的代碼數量越大,使用這些策略所帶來的性能提高也就越明顯。
5. 字符串和正則表達式:
(1) 在大多數瀏覽器中,數組項合併(Array.prototype.join)比其餘字符串鏈接方法更慢,但它卻在IE7及更早版本瀏覽器中合併大量字符串惟一高效的途徑;
(2) String.prototype.concat 比使用簡單的 + 和 += 稍慢,尤爲是在IE、Opera 和 Chrome 中慢的更明顯。此外,儘管使用 concat 合併字符串數組與前面討論的數組項鍊接相似,但它一般更慢一些(Opera 除外),而且它也潛伏着災難性的性能問題;
(3) 正則表達式的運行效率受許多因素影響。首先,正則表達式匹配的文本千差萬別,部分匹配比徹底不匹配所用的時間要長;不一樣的瀏覽器對正則表達式引擎有着不一樣程度的內部優化;