javascript的執行引擎是單線程的,正常狀況下是同步編程的模式,便是程序按照代碼的順序從上到下依次順序執行。只要有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段Javascript代碼長時間運行(好比死循環),那麼在執行期間任何UI更新都會被阻塞,界面事件處理也會中止響應。致使整個頁面卡在這個地方,其餘任務沒法執行。javascript
特別是在for循環語句裏,若是for循環的處理邏輯比較複雜,而且循環次數過多,超過1000次時,javascript的執行會阻塞瀏覽器處理起來會有明顯的假死狀態。緣由就是瀏覽器在調用javascript的時候,主界面是中止響應的,由於cpu交給js執行了,沒有時間去處理界面消息。html
爲了解決卡死的問題,不少人提出了異步編程的解決方案,這也是性能優化的其中一個方式,在瀏覽器端,耗時很長的操做都應該異步執行,避免瀏覽器失去響應。如今很火的nodejs就是異步編程,好比路由派發,IO操做,都是異步的。前端
在前端頁面實現中,最多見的異步就是ajax操做,請求一個ajax無需等待ajax返回,則可繼續操做頁面。java
其餘的還有經過setTimeout
,setInterval
,image.onload
, postMessage,webwork
等方式進行異步編程實現。node
網上也有不少庫實現了異步編程如:do.js
. step.js
, async.js
, flow.js
,就不詳細闡述了,有興趣的自行google瞭解。jquery
這裏主要講setTimeOut實現異步編程的方式。web
先看一段代碼,http://jsfiddle.net/jd_felix/mmrL66na/1/ajax
<!DOCTYPE html> <html> <head> <title>DEMO</title> </head> <script src="http://codeorigin.jquery.com/jquery-1.10.2.min.js"></script> <script> var updateSync = function() { for (var i = 0; i < 1000; i++) { $('#js_output').text(i); } } var updateAsync = function() { var i = 0; function updateLater() { $('#js_output').text(i++); if (i < 1000) { setTimeout(updateLater, 0); } } updateLater(); } </script> <body> <button onclick="updateSync()">同步DEMO</button> <button onclick="updateAsync()">異步DEMO</button> <div id="js_output"></div> </body> </html>
點擊同步DEMO:你會感受按鈕按下去的時候卡了一下,而後看到一個最終結果99999,而沒有中間過程,這就是由於在updateSync函數運行過程當中UI更新被阻塞,只有當它結束退出後纔會更新UI。編程
點擊異步DEMO:你會看到UI界面上從0到999快速地更新過程,這就是異步執行的結果。 函數裏先聲明瞭一個局部變量i和嵌套函數updateLater,而後調用了updateLater,在這個函數中先是更新output結點的內容爲i, 而後經過setTimeout讓updateLater函數異步執行。這實際是一種遞歸調用。任何for循環均可以改形成遞歸調用的形式。promise
這是由於雖然他的delay設置爲0,幾乎是即時觸發,但仍是被添加到了執行隊列後面,但就是這個過程,渲染已經完成了,當他回調函數執行時,輸出來的已是更新後的值了。
以上結果很顯然,異步操做不會阻塞UI,你能夠繼續執行瀏覽器其餘操做。讓UI操做更流暢,但異步編程也有壞處,如上面代碼,使用setTimeout
的異步方式,在代碼總體執行效率來看,要比同步執行耗時更長時間。同時因爲是異步執行,打斷了原有代碼的執行順序,形成嵌套的函數調用,破壞了原有的簡單邏輯,讓代碼難以讀懂。
在判斷是否執行完畢時,在同步編程中很方便實現,代碼寫在for循環後面就好了。而異步的話,則須要作一些判斷。
仍是以上的例子,如何在循環結束後執行回調?可使用Jquery
的$.when
,和$.Deferred
方法,固然也能夠本身寫回調函數,可是看起來沒那麼優雅。
var wait = function(){ var dtd = $.Deferred(); var i = 0; function updateLater() { $('#js_output').text(i++); if (i < 1000) { setTimeout(updateLater, 0); } if(i == 1000){ dtd.resolve(); // 改變Deferred對象的執行狀態 } } updateLater(); return dtd.promise(); // 返回promise對象 } var updateAsyncBack = function(){ $.when(wait()).done(function(){ alert('done!'); }) }
原文請看:http://faso.me/notes/20131123/javascript-synchronous-settimeout/