最佳化js指令碼設計,防止瀏覽器假死

來源:互聯網
上載者:User

標籤:線程   死迴圈   context   鎖死   back   分解   something   type   執行   

在Web開發的時候經常會遇到瀏覽器不響應事件進入假死狀態,甚至彈出“指令碼已耗用時間過長“的提示框,如果出現這種情況說明你的指令碼已經失控了,必須進行最佳化。

為什麼會出現這種情況呢,我們先來看一下瀏覽器的核心處理方式:


瀏覽器的核心是多線程的,它們在核心制控下相互配合以保持同步,一個瀏覽器至少實現三個常駐線程:javascript引擎線程,GUI渲染線程,瀏覽器事件觸發線程。

JavaScript引擎是基於事件驅動單線程執行的,JS引擎一直等待著任務隊列中任務的到來然後加以處理,瀏覽器無論再什麼時候都只有一個JS線程在運行JS程式。
GUI 渲染線程負責渲染瀏覽器介面,當介面需要重繪(Repaint)或由於某種操作引發迴流(reflow)時,該線程就會執行。但需要注意 GUI渲染線程與JS引擎是互斥的,當JS引擎執行時GUI線程會被掛起,GUI更新會被儲存在一個隊列中等到JS引擎空閑時立即被執行。
事件觸發線程,當一個事件被觸發時該線程會把事件添加到待處理隊列的隊尾,等待JS引擎的處理。這些事件可來自JavaScript引擎當前執行的代碼塊如setTimeOut、也可來自瀏覽器核心的其他線程如滑鼠點擊、AJAX非同步請求等,但由於JS的單線程關係所有這些事件都得排隊等待JS引擎處理。
瞭解了瀏覽器的核心處理方式就不難理解瀏覽器為什麼會進入假死狀態了,當一段JS指令碼長時間佔用著處理機就會掛起瀏覽器的GUI更新,而後面的事件響應也被排在隊列中得不到處理,從而造成了瀏覽器被鎖定進入假死狀態。另外JS指令碼中進行了DOM操作,一旦JS調用結束就會馬上進行一次GUI渲染,然後才開始執行下一個任務,所以JS中大量的DOM操作也會導致事件響應緩慢甚至真正卡死瀏覽器,如在IE6下一次插入大量的HTML。而如果真的彈出了“指令碼已耗用時間過長“的提示框則說明你的JS指令碼肯定有死迴圈或者進行過深的遞迴操作了。

Nicholas C. Zakas認為不論什麼指令碼,在任何時間、任何瀏覽器上執行都不應該超過100毫秒,否則一定要將指令碼分解成若干更小的程式碼片段。那麼我們該如何來做呢:

第一步,最佳化你的迴圈,迴圈體中包含太多的操作和迴圈的次數過多都會導致迴圈執行時間過長,並直接導致鎖死瀏覽器。如果迴圈之後沒有其他動作,每次迴圈只處理一個數值,而且不依賴於上一次迴圈的結果則可以對迴圈進行拆解,看下面的chunk的函數:

function chunk(array, process, context) {
  setTimeout(function() {
    var item = array.shift();
    process.call(context, item);
    if (array.length > 0) {
      setTimeout(arguments.callee, 100);

    }
  }), 100);
}
chunk()函數的用途就是將一個數組分成小塊處理,它接受三個參數:要處理的數組,處理函數以及可選的上下文環境。每次函數都會將數組中第一個對象取出交給process函數處理,如果數組中還有對象沒有被處理則啟動下一個timer,直到數組處理完。這樣可保證指令碼不會長時間佔用處理機,使瀏覽器出一個高響應的流暢狀態。

其實在我看來,藉助JS強大的閉包機制任何迴圈都是可拆分的,下面的版本增加了callback機制,使可再迴圈處理完畢之後進行其他的操作。

function chunk(array,process,cbfun){
  var i=0,len = array.length; //這裡要注意在執行過程中數組最好是不變的
  setTimeout(function(){
    process( array[i] , i++ ); //迴圈體要做的操作
    if( i < len ){
      setTimeout(arguments.callee,100);
    }else{
      cbfun(); //迴圈結束之後要做的操作
    }
  }, 100);
}
第二步,最佳化你的函數,如果函數體內有太多不相干但又要一起執行的操作則可以進行拆分,考慮下面的函數:

function dosomething(){
  dosomething1();
  dosomething2();
}
dosomething1和dosomething2互不相干,執行沒有先後次序,可用前面提到的chunk函數進行拆分:

function dosomething(){
  chunk([dosomething1,dosomething2],function(item){item();})
}
或者直接交給瀏覽器去調度

function dosome(){
setTimeout(dosomething1,0);
setTimeout(dosomething2,0);
}
第三步,最佳化遞迴操作,函數遞迴雖然簡單直接但是過深的遞迴操作不但影響效能而且稍不注意就會導致瀏覽器彈出指令碼失控對話方塊,必須小心處理。

看以下斐波那契數列的遞迴演算法:

function fibonacci(n) {
  return n < 2 ? n: fibonacci(n - 1) + fibonacci(n - 2);
};
fibonacci(40)這條語句將重複調用自身331160280次,在瀏覽器中執行必然導致指令碼失控,而採用下面的演算法則只需要調用40次

fibonacci = function(n){
  var memo = {0:0,1:0}; //計算結果緩衝
  var shell = function(n){
    var result = memo[n];
    if( typeof result != ‘number‘ ) {//如果值沒有被計算則進行計算
      memo[n] = shell(n-1) + shell(n -2)
    }

    return memo[n];
  }
  return shell(n);
}
這項技術被稱為memoization,他的原理很簡單就是同樣的結果你沒必要計算兩次。另一種消除遞迴的辦法就是利用迭代,遞迴和迭代經常會被作為互相彌補的方法。

第四步,減少DOM操作,DOM操作的代價是相當昂貴的,大多數DOM操作都會觸發瀏覽器的迴流(reflow)操作。例如添加刪除節點,修改元素樣式,擷取需要經過計算的元素樣式等。我們要做的就是盡量少的觸發迴流操作。

el.style.width = ‘300px‘ el.style.height = ‘300px‘ el.style.backgroundColor = ‘red‘
上面的操作會觸發瀏覽器的三次迴流操作,再看下面的方式:

el.className = ‘newStyle‘
通過設定改元素的className一次設定多個樣式屬性,將樣式寫再CSS檔案中,只觸發一次迴流,達到了同樣是效果而且效率更高。因為瀏覽器最擅長的就是根據class設定樣式。

還有很多可以減少DOM操作的方法,在此就不多說了,但是一個基本的原則就是讓瀏覽器去做它自己擅長的事情,例如通過class來改變元素的屬性。

相信經過上面的最佳化的過程必定可以大大提高使用者體驗,不會出現瀏覽器被鎖死和彈出指令碼失控的對話方塊,使你的瀏覽器從繁重的任務中解放出來。需要指出的是上面這些最佳化並不是必須的,只有當一段指令碼的執行時間真的影響到了使用者體驗才需要進行。雖然它們讓使用者覺得指令碼的執行變快了,但其實完成同一個操作的時間可能被延長了,這些技術只是讓瀏覽器處於一個快速響應的狀態,使使用者瀏覽更流暢。

最佳化js指令碼設計,防止瀏覽器假死

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.