編寫高性能js注意點

該文章以收錄: 《JavaScript深刻探索之路》

前言

本文的一些注意的是我之前看書籍總結的,咱們通常人寫項目時,對咱們的影響並非很大,有時徹底能夠忽略,可是咱們知道這些總不是什麼壞處。javascript

js這個大衆語言我想,不少人多知道,它入門和簡單,但是想要深刻了解仍是要有必定的水平、 的,一樣的效果雖然均可以寫出來,但仍是性能和可維護性卻有很大差異。下來咱們就來總結一下書寫高質量js代碼的一些注意點。css

代碼維護是高成本的,若是咱們在開發代碼時,不注重代碼的規範,可讀性和可維護性,那麼未來帶給咱們的將是更大的開支。若是說你是給公司幹活,本身寫完就不用管了,這樣的思想很危險。不但對他人讀取你代碼時帶來困擾,同時也是限制本身發展的一個障礙。代碼終究是要給人去閱讀的。咱們整個團隊在書寫代碼時應該勁量作到:java

  • 可讀的
  • 一致的
  • 可預測的
  • 看上去就像是同一我的寫的
  • 已記錄

執行與加載

管理瀏覽器中的腳本 JavaScript 代碼是個棘手的問題,由於代碼執行過程會阻塞瀏覽器的其餘進程,好比用戶界面繪製,每次遇到<script>標籤,頁面都必須停下來等待代碼下載(若是是外鏈文件)並執行,而後繼續處理其餘部分,儘管如此,仍是有幾種方法能減小javascript 對性能的影響。web

  1. </body>閉合標籤以前,將全部的<script>標籤放到頁面底部,這確保在腳本執行前頁面已加載完成了渲染。
  2. 合併腳本,頁面中的<script> 標籤越少,加載也就也快,響應也更迅速。不管外鏈文件仍是內嵌腳本都是如此
  3. 有多種無阻塞下載JavaScript方法:
  • 使用<script>標籤的defer屬性(H5提供一個新屬性async)

defer和async的相同點是採用並行下載,在下載過程當中不會產生阻塞。區別在於執行時機,async是加載完成後自動執行,而defer須要等待頁面完成後執行。算法

<script type="text/javascript" defer></script>
<script type="text/javascript" async></script>
  • 使用動態建立<script>元素來下載並執行代碼
    使用動態建立<script>元素不會阻塞頁面的其餘進程,這樣咱們能夠將動態建立的<script>放到頁面的 <head>區域。
function(url){
    var script = document.creatElement('script');
    script.type = "text/javascript";
    script.src = url;
    document.getElementsByTagName('head')[0].appendChild(script);
}

咱們何時知道腳本已經加載完成呢?Firefox,Opera,Chrome和safari3爲咱們提供了一個onload事件來監聽,而IE 爲咱們提供了onreadystatechange 事件並根據readyState屬性來進行判斷數組

經過上面的方法,咱們能夠極大提升那些須要使用大量的JavaScript 的 web應用的實際性能瀏覽器

變量處理

咱們都知道,JavaScript經過函數管理做用域。在函數內部聲明的變量只在這個函數內部,函數外面不可用。另外一方面,全局變量就是在任何函數外面聲明的或是未聲明直接簡單使用的。咱們應該儘可能的減小全局變量,這樣避免全局變量過多而形成變量命名衝突,同時又佔用內存。緩存

聲明變量是咱們儘可能用var去聲明數據結構

function fun(){
    a = 1; // 這樣是不推薦的,由於a是隱式的全局變量
    var b = 2; // 這樣是推薦的
    var c = d = 5 // 這樣也是不推薦的,由於d是隱式的全局變量
}

減小全局變量咱們還可使用當即執行函數,下來咱們也會有章節去詳細的講解app

(function(){
    var a = 3;
    var b = 4;
})()

另外咱們應該知道變量提高原則,這個在後面的章節中咱們還會詳細的講解咱們在寫一些變量時,能夠這樣去寫。

function(){
    var a,
        b,
        c;

    a = 1;
    b = 2;
    c = 3;
}

咱們把經常使用的變量寫到最上面,能夠進行註釋,這樣更有利於維護咱們的代碼,同時防止變量在定義以前使用的邏輯錯誤。

DOM操做的注意點

腳本進行DOM的操做的代價是很昂貴的。它是富Web應用中最多見的性能瓶頸。瀏覽器中一般會把DOM 和 JavaScript(ECMAScript) 獨立實現。咱們每一次用js去操做DOM都會產生性能消耗,因此咱們儘量小的去處理DOM,這也是如今MVVM和MVC框架流行的一部分緣由。咱們在處理DOM時儘量這樣作。

1.最小化DOM訪問次數,儘量在JavaScript端處理

var ul = document.getElementById("ul");
 var a = "";
 for(var i=0;i<10;i++){
    a += "<li>i</li>";
 }
 ul.innerHTML = a;

將要添加的標籤存儲在變量a中,一次性加入ul中,這樣只訪問一次dom,下降了性能消耗。

2.若是須要屢次訪問某個DOM節點,請使用局部變量存儲它的引用

var doc = document; // 存儲document
doc.getElementById("div");

3.當心處理HTML集合,由於它實時連繫着底層文檔,把集合的長度緩存到一個變量中,並在迭代中使用它。若是須要常常操做集合,建議把它拷貝到一個數組中。

var divList = document.getElementsByTagName("div");

for(var i = 0,len = divList.length; i < len; i++){
    
}

這裏咱們將divList的長度去存儲在變量 len中,而不像下面這樣每次循環都要讀取一遍長度。

// 避免使用的例子
var divList = document.getElementsByTagName("div");

for(var i = 0; i < divList.length; i++){
    
}

4.若是可能的話,使用速度更快的API,好比querySelectorAll()firstElementChild

5.要留意重繪和重排;批量修改樣式時,'離線'操做DOM樹,使用緩存、並減小訪問佈局信息的次數
瀏覽器下載完頁面中的全部組件以後會解析並生成兩個內部數據結構

DOM樹: 表示頁面結構

渲染樹: 表示DOM節點如何顯示

當DOM的變化影響了元素的幾何屬性時(寬高)—— 好比改變框寬度或給段落增長文字,致使行數增長,瀏覽器須要從新計算元素的幾何屬性,一樣其餘元素的幾何屬性和位置也會所以受到影響,瀏覽器會是渲染樹中受到影響的部分失效,並從新構造渲染樹。這一過程稱爲 「重排(reflow)」。完成重排後,瀏覽器會從新繪製影響的部分到屏幕中,該過程稱爲 「重繪」。

那麼咱們該怎麼減小重排和重繪

  • 樣式統一處理
    咱們在改變一個元素樣式時,能夠統一處理
//這種寫法不推薦
  var el = document.getElementById("div");
  el.style.width = '100px';
  el.style.height = '100px';
  el.style.padding = '10px';

 //這種寫法推薦
  var el = document.getElementById("div");
  el.style.cssText = "width:100px;height:100px;padding:5px;";

下邊的方法很明顯只進行了一次重排,而上邊的重排了三次。使用cssText時會覆蓋前邊的style樣式,咱們能夠這樣作

el.style.cssText += "width:100px;height:100px;padding:5px;";

另外咱們還能夠將改變的樣式寫在css樣式表中,經過修改class來改變其樣式。

var el = document.getElementById("div");
  el.className = "active";
  • 脫離文檔流修改DOM

脫離文檔修改DOM的步驟就是

使元素脫離文檔流
對其應用多重改變
把元素帶回文檔中

這裏我有三種方法:

(1) 第一種:隱藏元素,應用修改,從新顯示

var el = document.getElementById("div");
  el.style.display = "none";
  el.style.width = '100px';
  el.style.height = '100px';
  el.style.padding = '10px';
  el.style.display = "block";

(2) 第二種:使用文檔片斷在當前DOM以外構建一個子樹,在把它拷貝迴文檔

var el = document.getElementById("ul");
  var fragment = document.createDocumentFragment();
  for(var i = 0; i < 50000; i++){
    fragment.appendChild(document.createElement("li"));
  }
  el.appenChild(fragment);

推薦使用這種方式。

(3) 第三種:將原始元素拷貝到一個脫離文檔的節點中,修改副本,完成後在提貨原始元素

var el = document.getElementById("div");
 var cloneDiv = el.choleNode(true);
 cloneDiv.appendChild = li;
 el.parentNode.replaceChild(clone,el);

以上脫離文檔流修改DOM的方法推薦第二種方法。

  • 減小渲染變化的排隊與刷新

因爲每次重排都會產生計算消耗,大多數瀏覽器經過列隊化修改並批量執行來優化重排過程。而咱們不知不覺中就使用了一些強制刷新隊列並要求計劃任務馬上執行的方法:

1. offsetTop、offsetLeft、offsetWidth、offsetHeight
 2. scrollTop、scrollLeft、scrollWidth、scrollHeight
 3. clientTop、clientLeft、clientWidht、clientHeight
 4. getComputedStyle() // currentStyle IE中

咱們在使用上面方法時要注意,瀏覽器爲了返回最新值,會刷新隊列應用所用變動,咱們應該減小他們的使用,若是使用他們咱們最好緩存佈局信息
例如咱們將滾輪滾動到頁面頂部:

var scroll = document.body.scrollTop; // 這裏直接緩存scrollTop
var set = setInterval(function(){
     scroll--; // 這裏不使用window.scrollTop--
     document.body.scrollTop = scroll;
     if(scroll < 0) {
        clearInterval(set);
     }
},100)

6.動畫中使用絕對定位。

對於用展開/摺疊的效果,咱們使用絕對定位,將其脫離文檔流,會是重繪更少些。

7.使用事件委託來減小事件處理器的數量。

若是你還不知道事件委託事什麼,請訪問:
http://blog.csdn.net/webxiaoma/article/details/53501616

算法和流程控制

這裏咱們討論的是循環和判斷,咱們直接寫推薦的書寫方法

1.循環體
循環包括 for循環、while循環、do-while循環、for-in循環。
其中for-in循環的性能明顯比其餘性能差些,另外ES6對於屬性循環有出了for-of循環,比起for-in循環性能要好一些。
咱們前面也說了,循環時,咱們最好存儲判斷的長度,另外遞減循環要比遞加循環速度至關要快。

2.判斷
判斷有:ifswitch;
當咱們作判斷時,最好是吧最有可能出現的放到前面,另外if-else-if不少的話,推薦使用switch去判斷,這樣更有利於減小性能的消耗。有時候咱們選擇嵌套if也不使用不少if-else-if連用的方法以下:

// 推薦
if(){
  if(){
    if(){

    }
  }
}

// 儘可能少用
if(){

} else if(){

} else if(){

} else if(){

} else if(){

} else if(){

}

對象和做用域

對象的注意點是:

1.儘可能不擴展內置原型(如給Object.prototype添加本身的方法),除非你肯定不會對你的團隊形成影響

2.訪問的做用域和對象越深,越消耗性能,咱們 儘可能減小,不過這並非硬性的要求,咱們儘可能減小那些沒必要要的深刻訪問。

其餘注意點

  1. 避免隱士類型轉換(有時候判斷須要考慮用全等號仍是雙等號)
var a = 0;
if(a === false){
    //不執行
}

if(a == false){
    //執行了
}

2.儘可能少的使用eval()

3.項目發佈時的代碼壓縮,js文件整合,圖片整合等等優化。

4.整個團隊的代碼書寫風格,要肯定。

結束

文章基本就寫到這裏,項目書寫的注意點不僅僅只有這些,其實對代碼的優化還有不少,還有你的經驗。不斷積累必定會寫成很優雅的代碼。

參考文獻:《高性能JavaScript》

相關文章
相關標籤/搜索