首頁  >  文章  >  web前端  >  jQuery同步Ajax帶來的UI執行緒阻塞問題的解決

jQuery同步Ajax帶來的UI執行緒阻塞問題的解決

黄舟
黄舟原創
2017-08-13 10:09:142148瀏覽

本篇文章主要介紹了jQuery同步Ajax帶來的UI線程阻塞問題及解決辦法,具有一定的參考價值,有興趣的可以了解一下

俗話說不作死就不會死,今天作死了一回,寫了一個比較二逼的函數,遇到了同步Ajax引起的UI線程阻塞問題,在此記錄一下。

事情起因是這樣的,因為頁面上有多個相似的非同步請求動作,本著提高程式碼可重用性的原則,我封裝了一個名為getData的函數,它接收不同參數,只負責取得數據,然後把數據return。基本的邏輯剝離出來是這樣的:


function getData1(){
    var result;
    $.ajax({
      url : 'p.php',
      async : false,
      success: function(data){
        result = data;
      }
    });

  return result;
}

這裡的ajax不能用異步的,否則函數返回時,result還未賦值,會出錯。所以我加了async:false。看起來好像沒什麼問題。我呼叫這個函數可以正常的得到資料。


$('.btn1').click(function(){
    var data = getData1();
    alert(data);
});

接下來,要加另一個功能,由於ajax請求有一定的耗時,所以我需要在發出請求前頁面有個loading效果,即顯示一張「正在載入」的gif圖片,想必大家也都看過。所以我的處理函數變成了這樣:


$('.btn1').click(function(){
    $('.loadingicon').show();
    var data = getData1();
    $('.loadingicon').hide();
    alert(data);
});

請求之前顯示loading圖片,請求完成後把它隱藏。看起來也沒什麼問題。為了看清效果,我的p.php程式碼sleep了3秒,如下:


<?php
sleep(3);
echo (&#39;aaaaaa&#39;);
?>

但是我運行的時候問題出現了,我點擊按鈕並未像預想的那樣出現這個loading圖片,頁面什麼反應都沒有。排除良久找到了原因,就在async:false這裡。

瀏覽器的渲染(UI)執行緒和js執行緒是互斥的,在執行js耗時操作時,頁面渲染會被阻塞掉。當我們執行非同步ajax的時候沒有問題,但當設定為同步請求時,其他的動作(ajax函數後面的程式碼,還有渲染線程)都會停止下來。即使我的DOM操作語句是在發起請求的前一句,這個同步請求也會「迅速」將UI執行緒阻塞,不給它執行的時間。這就是程式碼失效的原因。

setTimeout解決阻塞問題

既然明白了問題在哪裡,我們就來針對性想辦法。為了不讓同步ajax請求阻塞線程,我想到了setTimeout,把請求的程式碼放到sestTimeout中,讓瀏覽器重啟一個線程來操作,不就解決問題了嗎?於是乎,我的程式碼變成了這樣:


$(&#39;.btn2&#39;).click(function(){
    $(&#39;.loadingicon&#39;).show();
    setTimeout(function(){
      $.ajax({
        url : &#39;p.php&#39;,
        async : false,
        success: function(data){
          $(&#39;.loadingicon&#39;).hide();
          alert(data);
        }
      });
    }, 0);
});

setTimeout的第二個參數設為0,瀏覽器會在一個已設定的最小時間後執行。不管三七二十一先運行起來看看。

結果loading圖片顯示出來了,但是! ! !圖片怎麼不動呢,我明明是一張動態gif圖。這時候我很快就想到了,雖然同步請求延遲執行了,但是它執行期間還是會把UI線程給阻塞。這個阻塞相當牛逼,連gif圖片都不動了,看起來像一張靜態圖片一樣。

結論很明顯,setTimeout治標不治本,相當於把同步請求「稍稍」異步了一下,接下來還是會進入同步的惡夢,阻塞線程。方案失敗。

是時候用Deferred了

jQuery在1.5版本之後,引入了Deferred對象,提供的很方便的廣義異步機制。詳情可參考這篇文章http://www.jb51.net/article/54762.htm。

於是我用Deferred物件改寫了程式碼,如下:


function getData3(){
    var defer = $.Deferred();
    $.ajax({
      url : &#39;p.php&#39;,
      //async : false,
      success: function(data){
        defer.resolve(data)
      }
    });
    return defer.promise();
}  

$(&#39;.btn3&#39;).click(function(){
    $(&#39;.loadingicon&#39;).show();
    $.when(getData3()).done(function(data){
      $(&#39;.loadingicon&#39;).hide();
      alert(data);
    });
});

可以看到我在ajax請求中去掉了async:false,也就是說,這個請求又是異步的了。另外請注意success函數中的這句話:defer.resolve(data),Deferred物件的resolve方法可傳入一個參數,任意型別。這個參數可以在done方法中拿到,所以我們非同步請求來的資料就可以用這樣的方式來回傳了。

至此,問題得到了解決。 Deferred物件如此強大且方便,我們可以好好利用它。

我的完整測試程式碼如下,有意的同學可以拿去測一下:


#
<button class="btn1">async:false</button>
<button class="btn2">setTimeout</button>
<button class="btn3">deferred</button>
  
<img class="loadingicon" style="position:fixed;left:50%;top:50%;margin-left:-16px;margin-top:-16px;display:none;" src="loading2.gif" alt="正在加载" />
<script>

  function getData1(){
    var result;
    $.ajax({
      url : &#39;p.php&#39;,
      async : false,
      success: function(data){
        result = data;
      }
    });

    return result;
  }

  $(&#39;.btn1&#39;).click(function(){
    $(&#39;.loadingicon&#39;).show();
    var data = getData1();
    $(&#39;.loadingicon&#39;).hide();
    alert(data);
  });


  
  $(&#39;.btn2&#39;).click(function(){
    $(&#39;.loadingicon&#39;).show();
    setTimeout(function(){
      $.ajax({
        url : &#39;p.php&#39;,
        async : false,
        success: function(data){
          $(&#39;.loadingicon&#39;).hide();
          alert(data);
        }
      });
    }, 0);
  });



  function getData3(){
    var defer = $.Deferred();
    $.ajax({
      url : &#39;p.php&#39;,
      //async : false,
      success: function(data){
        defer.resolve(data)
      }
    });
    return defer.promise();
  }  

  $(&#39;.btn3&#39;).click(function(){
    $(&#39;.loadingicon&#39;).show();
    $.when(getData3()).done(function(data){
      $(&#39;.loadingicon&#39;).hide();
      alert(data);
    });
  });</script>

PS:Firefox有做優化?

上述問題在chrome和IE9中測試結論一致。但是我在Firefox中測試時,同步ajax並未阻塞掉UI線程,也就是說這個問題根本不存在。我用其他程式碼做了測試,在Firefox中js線程確實是會阻塞UI線程,這個沒有疑問。那可能的猜測就是Firefox對同步ajax做了優化,事實到底是什麼,我暫未得知。有高人知道還請指點點。

以上是jQuery同步Ajax帶來的UI執行緒阻塞問題的解決的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn