你可能知道,Javascript
語言的執行環境是"單線程"(single thread)。
所謂"單線程",就是指一次只能完成一件任務。若是有多個任務,就必須排隊,前面一個任務完成,再執行後面一個任務,以此類推。
這種模式的好處是實現起來比較簡單,執行環境相對單純;壞處是隻要有一個任務耗時很長,後面的任務都必須排隊等着,會拖延整個程序的執行。常見的瀏覽器無響應(假死),每每就是由於某一段Javascript
代碼長時間運行(好比死循環),致使整個頁面卡在這個地方,其餘任務沒法執行。
爲了解決這個問題,Javascript
語言將任務的執行模式分紅兩種:同步(Synchronous)和異步(Asynchronous).
"異步模式"很是重要。在瀏覽器端,耗時很長的操做都應該異步執行,避免瀏覽器失去響應,最好的例子就是Ajax
操做。在服務器端,"異步模式"甚至是惟一的模式,由於執行環境是單線程的,若是容許同步執行全部http
請求,服務器性能會急劇降低,很快就會失去響應。javascript
setTimeout
函數的弊端延時處理固然少不了 setTimeout
這個神器,不少人對 setTimeout
函數的理解就是:延時爲 n 的話,函數會在 n 毫秒以後執行。事實上並不是如此,這裏存在三個問題:
一個是 setTimeout
函數的及時性問題, setTimeout
是存在必定時間間隔的,並非設定 n 毫秒執行,他就是 n 毫秒執行,可能會有一點時間的延遲,setInterval
和 setTimeout
函數運轉的最短週期是 5ms 左右,這個數值在 HTML規範 中也是有提到的:css
Let timeout be the second method argument, or zero if the argument was omitted.
若是 timeout 參數沒有寫,默認爲 0html
If nesting level is greater than 5, and timeout is less than 4, then increase timeout to 若是嵌套的層次大於 5 ,而且 timeout 設置的數值小於 4 則直接取 4.java
其次是while
循環會阻塞setTimeout
的執行jquery
看這段代碼:
var t = true; window.setTimeout(function (){ t = false; },1000); while (t){} alert('end');
結果是死循環致使setTimeout
不執行,也致使alert
不執行
js是單線程,因此會先執行while(t){}
再alert
,但這個循環體是死循環,因此永遠不會執行alert
。
至於說爲何不執行setTimeout
,是由於js的工做機制是:當線程中沒有執行任何同步代碼的前提下才會執行異步代碼,setTimeout
是異步代碼,因此setTimeout
只能等js空閒纔會執行,但死循環是永遠不會空閒的,因此setTimeout
也永遠不會執行。git
第三是,try..catch
捕捉不到他的錯誤github
這是異步編程最基本的方法。
假定有兩個函數f1和f2,後者等待前者的執行結果。面試
function f1(callback){ setTimeout(function () { // f1的任務代碼 callback(); }, 1000); } f1(f2);
採用這種方式,咱們把同步操做變成了異步操做,f1不會堵塞程序運行,至關於先執行程序的主要邏輯,將耗時的操做推遲執行。
回調函數的優勢是簡單、容易理解和部署,缺點是不利於代碼的閱讀和維護,各個部分之間高度耦合(Coupling),流程會很混亂,並且每一個任務只能指定一個回調函數。ajax
另外一種思路是採用事件驅動模式。任務的執行不取決於代碼的順序,而取決於某個事件是否發生數據庫
f1.on('done', f2); function f1(){ setTimeout(function () { // f1的任務代碼 f1.trigger('done'); }, 1000); }
JS 和 瀏覽器提供的原生方法基本都是基於事件觸發機制的,耦合度很低,不過事件不能獲得流程控制
Promises
對象Promises
對象是CommonJS
工做組提出的一種規範,目的是爲異步編程提供統一接口。
Promises
能夠簡單理解爲一個事務,這個事務存在三種狀態:
已經完成了 resolved
由於某種緣由被中斷了 rejected
還在等待上一個事務結束 pending
簡單說,它的思想是,每個異步任務返回一個Promises
對象,該對象有一個then
方法,容許指定回調函數,這樣寫的優勢在於,回調函數變成了鏈式寫法,程序的流程能夠看得很清楚
Promises
就是一個事務的管理器。他的做用就是將各類內嵌回調的事務用流水形式表達,其目的是爲了簡化編程,讓代碼邏輯更加清晰。
Promises
能夠分爲:
無錯誤傳遞的 Promises
,也就是事務不會由於任何緣由中斷,事務隊列中的事項都會被依次處理,此過程當中 Promises
只有 pending
和 resolved
兩種狀態,沒有 rejected
狀態。
包含錯誤的 Promises
,每一個事務的處理都必須使用容錯機制來獲取結果,一旦出錯,就會將錯誤信息傳遞給下一個事務,若是錯誤信息會影響下一個事務,則下一個事務也會 rejected
,若是不會,下一個事務能夠正常執行,依次類推。
此處留坑講generator實現異步編程
原本想本身總結下generator與異步的,看了下阮一峯老師的博客算是瞭解個大概,理解也是隻知其一;不知其二,有興趣的話能夠在底下的參考資料裏找到去看看
jquery
的Deferred
對象簡單說,Deferred
對象就是jquery
的回調函數解決方案。在英語中,defer的意思是"延遲",因此Deferred
對象的含義就是"延遲"到將來某個點再執行。
首先,回顧一下jquery
的ajax操做的傳統寫法:
$.ajax({ url: "test.html", success: function(){ alert("哈哈,成功了!"); }, error:function(){ alert("出錯啦!"); } });
有了<ode>Deferred對象之後,寫法是這樣的:
$.ajax("test.html") .done(function(){ alert("哈哈,成功了!"); }) .fail(function(){ alert("出錯啦!"); });
能夠看到,done()
至關於success
方法,fail()
至關於error
方法。採用鏈式寫法之後,代碼的可讀性大大提升。
瞭解jQuery.Deferred
對象能夠看下面這個表格。
when.js
AngularJS
內置的Kris Kowal的Q框架,和cujoJS的when.js
,二者都是Promises/A
規範的實現when.js
實例
var getData = function() { var deferred = when.defer(); $.getJSON(api, function(data){ deferred.resolve(data[0]); }); return deferred.promise; } var getImg = function(src) { var deferred = when.defer(); var img = new Image(); img.onload = function() { deferred.resolve(img); }; img.src = src; return deferred.promise; } var showImg = function(img) { $(img).appendTo($('#container')); } getData() .then(getImg) .then(showImg);
看最後三行代碼,是否是一目瞭然,很是的語義化
var deferred = when.defer();
定義了一個deferred
對象。
deferred.resolve(data);
在異步獲取數據完成時,把數據做爲參數,調用deferred
對象的resolve
方法。
return deferred.promise;
返回了deferred
對象的Promises
屬性。
step.js
github地址step.js
是控制流程工具(大小僅 150 行代碼),解決回調嵌套層次過多等問題。適用於讀文件、查詢數據庫等回調函數相互依賴,或者分別獲取內容最後組合數據返回等應用情景。異步執行簡單地能夠分爲「串行執行」和「並行」執行
使用示例:
Step( function readSelf() { fs.readFile(__filename, this); }, function capitalize(err, text) { if (err) throw err; return text.toUpperCase(); }, function showIt(err, newText) { if (err) throw err; console.log(newText); } );
Step
的一個約定,回調函數的第一個參數老是 err,第二個纔是值(沿用 Node
回調的風格)。若是上一個步驟發生異常,那麼異常對象將被送入到下一個步驟中。
Javascript
既是單線程又是異步的,請問這兩者是否衝突,以及有什麼區別?Answer1:Javascript
自己是單線程的,並無異步的特性。
因爲 Javascript
的運用場景是瀏覽器,瀏覽器自己是典型的 GUI 工做線程,GUI 工做線程在絕大多數系統中都實現爲事件處理,避免阻塞交互,所以產生了 Javascript
異步基因。此後種種都源於此。
Answer2: JS的單線程是指一個瀏覽器進程中只有一個JS的執行線程,同一時刻內只會有一段代碼在執行(你可使用IE的標籤式瀏覽試試看效果,這時打開的多個頁面使用的都是同一個JS執行線程,若是其中一個頁面在執行一個運算量較大的function時,其餘窗口的JS就會中止工做)。
而異步機制是瀏覽器的兩個或以上常駐線程共同完成的,例如異步請求是由兩個常駐線程:JS執行線程和事件觸發線程共同完成的,JS的執行線程發起異步請求(這時瀏覽器會開一條新的HTTP請求線程來執行請求,這時JS的任務已完成,繼續執行線程隊列中剩下的其餘任務),而後在將來的某一時刻事件觸發線程監視到以前的發起的HTTP請求已完成,它就會把完成事件插入到JS執行隊列的尾部等待JS處理。又例如定時觸發(setTimeout
和setinterval
)是由瀏覽器的定時器線程執行的定時計數,而後在定時時間把定時處理函數的執行請求插入到JS執行隊列的尾端(因此用這兩個函數的時候,實際的執行時間是大於或等於指定時間的,不保證能準肯定時的)。
因此,所謂的JS的單線程和異步更多的應該是屬於瀏覽器的行爲,他們之間沒有衝突,更不是同一種事物,沒有什麼區別不區別的。
setTimeout(fn,0)
當即執行的問題首先,不會當即執行,緣由:setTimeout(fn,0)
的做用很簡單,就是爲了把fn放到運行隊列的最後去執行。也就是說,不管setTimeout(fn,0)
寫在哪,均可以保證在隊列的最後執行。js解析器會把setTimeout(fn,0)
裏的fn壓到隊列的最後,由於它是異步操做。有個延時,具體是16ms仍是4ms取決於瀏覽器
當即執行仍是有可能的,只要在你調用setTimeout
的時候,知足下面兩個條件:
恰好執行到了當前這一輪事件循環的底部。
恰好此時事件隊列爲空。
那麼setTimeout
的回調函數就能夠當即執行。固然「當即執行」的意思是在任何其餘代碼前執行。
知乎:setTimeout的異步以及js是單線程的面試題?
阮老師的博客
小鬍子哥的博客
知乎:JavaScript 既是單線程又是異步的,請問這兩者是否衝突,以及有什麼區別?
細嗅Promise
whenjs文檔
jQuery的deferred對象詳解
jQuery 中的 Deferred 和 Promises (2)
Step.js 使用教程(附源碼解析)
Generator 函數的含義與用法