[ JS 進階 ] 如何改進代碼性能 (3)

原本在那片編寫可維護性代碼文章後就要總結這篇代碼性能文章的,耽擱了幾天,原本也是決定天天都要更新一篇文章的,由於之前欠下太多東西沒總結,學過的東西沒去總結真的很快就忘記了,記錄一下在你腦力留下更深的印象,特別是這些可維護性代碼,性能什麼的,當在你腦子裏造成一種習慣了,那你就牛了!這裏也要給初學者一個建議:多總結你學過的東西,由於這其實也是在學習新知識! 好,進入咱們的主題:如何提升JS代碼的性能。javascript

在今天的web應用中,應用了大量的Javascript,所以代碼的執行效率變得尤其重要,也就是性能!爲了提升JS的性能,咱們應該掌握一些基本的性能優化方式,並讓它成爲咱們書寫代碼的習慣。下面介紹幾種優化性能的方式,不少初學者甚至有經驗的開發者也會忽略,但願對你有幫助!html

1.優化DOM交互

DOM與咱們的頁面緊密相關,瀏覽器渲染頁面也就是在渲染解析後的DOM元素,DOM操做與交互要消耗大量的時間,由於它們每每須要從新渲染整個頁面或者一部分。進一步說,看似細微的一些操做也可能須要花不少時間來執行,由於DOM要處理的信息很是多,所以咱們應該儘量地優化與DOM相關的操做,加快瀏覽器對頁面的渲染!爲何有些DOM操做會影響頁面性能,能夠查看我寫的一些關於瀏覽器原理的文章:java

ok,優化DOM操做,咱們主要有一些幾種方式:web

1.1 最小化現場更新

什麼是DOM的現場更新:須要對DOM部分已經顯示的頁面的一部分的顯示當即更新。可是,每個更改,不論是插入單個字符,仍是一處整個片斷,都有必定的性能懲罰,由於瀏覽器須要從新計算無數尺寸以進行更新(相關知識請閱讀:)。因此,現場更新進行的越多,代碼執行所花的時間就越長,反之代碼執行越快,以下:算法

var list = document.getElementById('mylist'),
           item,
           i;
for(i = 0; i < 10; i++){
    item = document.creatElement('li');
    list.appendChild(item);
    item.appendChild(document.creatTextNode('item' + i));
}

這段代碼爲列表mylist添加了10個項目,沒添加一個項目都要進行2次的現場更新:添加元素和添加文本節點,因此這個操做一個須要完成20個現場更新,每一個更新都會損失性能,可見這樣的代碼運行起來是相對緩慢的。編程

解決的方法是使用文檔碎片間接地更改DOM元素:數組

var list = document.getElementById('mylist'),
           fragment = document.creatDocumentFragment(),
           item,
           i;
for(i = 0; i < 10; i++){
    item = document.creatElement('li');
    fragment .appendChild(item);
    item.appendChild(document.creatTextNode('item' + i));
}
list.appendChild(fragment);

像這樣的代碼只需進行一次的現場更新。記住,當給appendChild()傳入文檔碎片是,只有文檔碎片中的子節點纔會被添加到目標元素,碎片自己不會被添加。瀏覽器

如今,你應該明白你用循環直接進行DOM節點的增刪查改是多麼對不起瀏覽器的事了吧 `(∩_∩)′ 。性能優化

1.2 使用 innerHTML

除了上面代碼中使用的creatElement()appendChild()結合的方法建立DOM元素以外,還有經過給innerHTML賦值來建立。對於小的DOM更改而言,兩種方法的效率其實差很少,但對於大量的DOM節點的更改,後者要比前者快得多!爲啥捏?app

由於當咱們給innerHTML賦值時,後臺會建立一個HTML解析器,而後使用內部的DOM調用來建立DOM結構,而非基於JavascriptDOM調用,因爲內部方法是編譯好的而非解釋執行的,因此執行代碼的速度要快不少!

innerHTML改寫上面的例子:

var list = document.getElementById('mylist'),
              html = '', //聲明一個空字符串
               i;
    for(i = 0; i < 10; i++){
        html += '<li>item' + i + '</li>';
    }
    list.innerHTML = html; // 這裏記得innerHTML後面的HTML四個字母都要大寫!

這種方式一樣也只進行了一次的現場更新,而且性能要比上一種方式要好!雖然在字符串的連接上有點性能損失。

1.3 使用事件代理/事件委託

事件處理程序爲web應用提供交互能力,所以許多開發人員會不分青紅皁白地向頁面中添加大量的處理程序,有個問題就是一個頁面上的事件處理程序數量將直接關係到頁面的總體運行性能。爲何捏?

首先,事件處理程序對應至少一個函數,JS中每一個函數都是對象,都會佔用內存,內存中的對象越多,性能就越差。

其次,咱們必須事先指定全部事件處理程序,這就致使了DOM訪問次數增多,會延遲整個頁面的交互就緒時間,頁面響應用戶操做變得相對緩慢。

因此減小事件處理程序一樣也可讓咱們的頁面更牛暢!使用事件委託勢在必得啊!

事件委託的原理其實就是事件冒泡,只指定一個事件處理程序就能夠管理某一類型操做的全部事件。例如:click事件會一直冒泡到document層次,也就是說咱們沒必要爲每一個元素添加事件,只需在較高的層次的元素上添加事件處理程序便可,而後利用事件對象(event)的屬性或方法去判斷當前點擊的元素,而後作出相應的響應。這個我就不展開講了,初學者能夠自行查閱事件冒泡知識

2.做用域很重要

說到做用域啊就很容易想到做用域鏈(scope chain),咱們知道要搜索一個變量,所在的執行環境都要沿着這條做用域向上搜索這個變量,做用域鏈上有不少的變量,那麼咱們就得遍歷,遍歷就須要時間啊,並且你越往上查找所需時間越多,若是咱們能減小這個時間,咱們代碼執行效率不是能夠提升了嗎?

好聰明啊,ok,我看看有哪些方式能夠減小這個時間:

2.1 避免全局查找

這是性能優化的一重點,上面也說了,越往上查找時間越多,也就是說查找全局變量和函數比局部要多!看代碼:

function updateUI(){
    var imgs = document.getElementByTagName('img');
    for(var i = 0 ,lng = imgs.length;i < lng;i ++){
        imgss[i].title = document.title + 'image' + i;
    }
    var msg = docuement.getElementById('msg');
    msg.innerHTML = 'update complete.';
}

這代碼很正常呀!我以前也常常這麼作滴。可是咱們細心能夠發現,這段代碼有三處引用了全局變量document,若是咱們的頁面不少圖片,那麼在for循環中的document就會被執行上百次,而每次都要須要在做用域鏈中查找,時間都去哪了,我還沒......停!。

咱們能夠經過在函數中建立一個局部變量保存對document的引用,這樣,咱們在函數裏任何地方引用document都不用跑到全局變量去找了。這樣就改進了代碼的性能,看代碼:

function updateUI(){
    var doc = document; // 將document保存在局部變量doc中
    var imgs = doc.getElementByTagName('img');
    for(var i = 0 ,lng = imgs.length;i < lng;i ++){
        imgss[i].title = doc.title + 'image' + i;
    }
    var msg = doc.getElementById('msg');
    msg.innerHTML = 'update complete.';
}

因此啊,咱們在開發中,若是在函數中會常常用到全局變量,把它保存在局部變量中!

2.2 避免使用with語句

用with語句延長了做用域,查找變量一樣費時間,這個咱們通常不會用到,因此不展開了。解決方法仍是和上面的例子同樣,將全局變量保存在局部變量中!

3.優化循環

循環在編程中可謂屢見不鮮,在js中也隨處可見,循環體會反覆地執行同一段代碼,執行時間一直累加,因此可以對循環體的代碼進行優化也能夠大大減小執行時間!如何優化?四種方式。

3.1 減值迭代

咱們寫迭代器(循環條件)的時候通常都這樣(var i = 0;i < 10;i ++),從0開始,增長到某個特定值。然而在不少狀況下,若是在循環中使用減值迭代器效率更高。我測試了下,若是循環體不復雜的話,二者差很少!

//增值迭代 --效率較低
for(var i = 0;i < items.length;i++){
   doSomething(items[i]); 
}
//減值迭代 --效率較高
for(var i = items.length - 1;i >= 0;i--){
   doSomething(items[i]); 
}

3.2 簡化終止條件

因爲每次循環都會計算終止條件,因此必須保證它的執行儘量地塊。這裏主要是避免其餘DOM元素及其屬性的的查找。

//看終止條件,每次循環都須要查詢items及其length屬性
for(var i = 0;i < items.length;i++){
   doSomething(items[i]); 
}

//將items.length的值保存在局部變量lng中。
for(var i = 0,lng = items.length;i < lng;i++){
   doSomething(items[i]); 
}

3.3 簡化循環體

緣由和上面以上的,因此在循環體內避免大量的密集的操做。

這其實和上面講的:1.1 最小化現場更新 。是同樣的優化方式。能夠倒回去看看。

4.基本的算法優化

在計算機中,算法的複雜度用O表示。下面是javascript中幾種常見的算法類型:

  1. O(1) :常數,無論有多少值,執行的時間都是恆定的,好比簡單值和存儲在變量中的值。
  2. O(log n):對數,總的執行時間和數量有關,但不必定要獲取每個值,如:二分法查找
  3. O(n) :線性,總執行時間和數量直接相關,如:遍歷
  4. O(n*n) :平方,總執行時間和數量有關,每一個值至少獲取N次,如:插入排序

ok,有了上面的知識,咱們就能夠對javascript進行一些算法上的優化了。看代碼:

var value = 5;
var sum = value + 10;
alert(sum);

這段代碼進行了4次常量值的查找:數字5,變量value,數字10,變量sum,這段代碼的算法複雜度就是O(1)。又如:

var value = [10,5];
var sum = value[0] + value[1];
alert(sum);

在javascript中訪問數組元素也是一個O(1)操做,和簡單的變量查找效率同樣。再看:

var value = {one:10,two:10};
var sum = value.one + value.two;
alert(sum);

要表達的是訪問對象上的屬性要比訪問數組和變量的效率低。由於這是一個O(n)操做。你須要在對象的原型鏈中查找該屬性,所花時間較多。

好了,看完這個是否是感受眼前一片光明啊。其實咱們前面所講的要把常常用到的全局屬性保存在一個局部變量中就是根據這個原理了,訪問全局屬性是一個O(n)的操做,而訪問變量是一個O(1)的操做,大聲告訴我,挖掘機哪家強啊!

5.最小化語句數

前面講的優化差很少都是和精簡優化語句有關的,是的,我以爲代碼的質量和數量就是性能的評判標準。前面講了一些代碼質量相關的優化,這裏就講講代碼數量的優化。

5.1 精簡變量聲明

//用了5條語句聲明5個變量
var count = 5;
var color = 'red';
var values = [1,2,3];
var now = new Date();

//用了1條語句聲明5個變量,注意每一個變量用逗號隔開
var count = 5,
    color = 'red',
    values = [1,2,3],
    now = new Date();

5.2 使用數組和對象字面量

// 建立兩個對象 ----很差的方式
//one 四條語句
var values = new Array();
values[0] = 123;
values[1] = 456;
values[2] = 789;
//two 四條語句
var person = new Object();
person.name = 'jozo';
person.age = 21;
person.sayName = function(){
    alert(this.name);
};
// 建立兩個對象 ----推薦的方式
//one 1條語句
var values = [123,456,789]
//two 1條語句
var person = {
    name : 'jozo',
    age : 21,
    sayName : function(){
    alert(this.name);
};

6.其餘

寫累了,若有不正確的地方請指正哦,還有一些其餘的優化,下次文章繼續!

相關文章
相關標籤/搜索