今天想做一個點擊地市用ajax重新獲取數據刷新頁面功能,因為ajax屬於耗時操作,想在獲取數據且加載頁面時顯示加載遮罩層,結果發現了ajax的好多坑。
例如如上栗子,我想點擊按鈕讓遮罩層顯示,ajax加載完畢后遮罩層消失。因為我想讓loadChart()在賦值操作后執行,但如果async設為true時,往往會先執行loadChart(),之后才會賦值,所以我只能將ajax設為同步。但同步后無論我怎么點按鈕,遮罩層都不會出來。將顯示遮罩放到beforeSend中也沒用。用layer.load()也一樣出不來。
原因就是ajax的async設置為true時,ajax會委托瀏覽器另起一個線程,此線程與js線程和ui線程不沖突,只是在執行完成后再插入js事件環。而ajax的async設置為false時並沒有啟動單獨的線程,還是在js主線程中執行,所以會與瀏覽器的渲染(UI)線程和js線程是互斥的,在執行js耗時操作時,頁面渲染會被阻塞掉。當我們執行異步ajax的時候沒有問題,但當設置為同步請求時,其他的動作(ajax函數后面的代碼,還有渲染線程)都會停止下來。即使我的DOM操作語句是在發起請求的前一句,這個同步請求也會“迅速”將UI線程阻塞,不給它執行的時間。這就是代碼失效的原因。
同樣在例如alert()中也會有這個坑。
上圖中頁面時先顯示helloWorld呢,還是先彈出1呢。結果是先彈出1,確定后才顯示helloWorld;因為alert也會阻塞UI線程,而且會搶在ui線程(dom操作)執行前進行(並不影響例如console.log()出現的順序)
比如這樣時console.log正常出現在alert()之前。
回歸原話題。這是我想到可能用setTimeout()讓ajax一邊執行去,或許不會阻塞線程。但還是錯了
JavaScript是單線程執行的,無法同時執行多段代碼。當某一段代碼正在執行的時候,所有后續的任務都必須等待,形成一個隊列。一旦當前任務執行完畢,再從隊列中取出下一個任務,這也常被稱為 “阻塞式執行”。所以一次鼠標點擊,或是計時器到達時間點,或是Ajax請求完成觸發了回調函數,這些事件處理程序或回調函數都不會立即運行,而是立即排隊,一旦線程有空閑就執行。假如當前 JavaScript線程正在執行一段很耗時的代碼,此時發生了一次鼠標點擊,那么事件處理程序就被阻塞,用戶也無法立即看到反饋,事件處理程序會被放入任務隊列,直到前面的代碼結束以后才會開始執行。如果代碼中設定了一個 setTimeout,那么瀏覽器便會在合適的時間,將代碼插入任務隊列,如果這個時間設為 0,就代表立即插入隊列,但不是立即執行,仍然要等待前面代碼執行完畢。所以 setTimeout 並不能保證執行的時間,是否及時執行取決於 JavaScript 線程是擁擠還是空閑。
由上可知setTimeout並不是異步的,而是將其操作插入到js線程中,排隊執行,造成異步的假象,這時setTimeout立即將ajax排到js線程中,仍然會造成ui阻塞,遮罩層還是出不來。
查了半天,我發現以前沒注意的東西,叫deferred對象。
具體內容可以看大牛阮一峰老師博客:
http://www.ruanyifeng.com/blog/2011/08/a_detailed_explanation_of_jquery_deferred_object.html
<script> var data; function toGetData() { var defer = $.Deferred(); $.ajax({ url: 'xxx', type: "post", // 請求類型 data: { }, dataType: 'json', async: true, // 是否異步 success: function (ret) { if (ret) { data=ret; defer.resolve(ret) } else { alert("無數據"); } } }); return defer; } $('button').click(function(){ $(".shadow").show() $.when(toGetData()).done(function(ret){ loadChart() $(".shodow").hide() //所有的ajax的邏輯可以在這個地方進行處理 }); }) </script>
總之,想讓ajax走完再加載頁面,就要使用同步。但是只要同步,ajax就會阻塞ui線程,使得loading顯示不出來。
只有使用了deffer對象和$.when(),既可以ajax設為異步,保證了loading的正常顯示,又可以保證在ajax走完再加載頁面。因為$.when().done()會在deffer.resolve()之前的代碼全部走完后才走done中的代碼。
我改成這樣。由於ajax為同步時點擊切換比較卡。能用異步最好還是用異步,用defferred對象后就可以把async換成true了。$.when()函數只接受defferred對象,所以我們在toGetData中需要先創建對象,再return就解決了。defer.resolve(ret)用於控制ajax何時結束,比如我執行完賦值操作結束ajax,進入.done()中的回調函數,它還可以把數據ret也帶出來使用,這里我沒有用到,這里執行完loadChart()操作后遮罩層消失。所以它能保證deffer.resolve之前的代碼執行完再執行回到函數,async設為true也沒任何影響。
這樣就完美解決了因為ajax阻塞線程導致loading層出不來的問題啦。
轉自:https://blog.csdn.net/lianzhang861/article/details/79426385