學會使用JavaScript的異步及優化
文章目錄
0.異步的認識對比並行機制
0.1異步的定義
指程序的一部分現在運行,而另一部分則在將來運行,現在與將來之間有時間空隙,在此間隙中程序沒有活躍執行。
異步編程指的就是程序現在運行的部分和將來運行的部分之間的關係,對比for循環,雖然執行也需要幾微秒的時間,卻可以稱之爲同步的而非異步執行!!
0.2異步的思考
- 如何實現異步,有多少方法?
//只需要將一段代碼包裝爲一個函數(包括回調函數),並指定其在響應某個事件(定時器,鼠標點擊,Ajax響應等)
//執行,意味着你已經創建了一個將來執行的代碼塊,也就在此程序中引入了異步機制!!
setTimeout(function later(){
console.log("我被異步執行了!")
},1000)
function later(){
console.log("將來纔會被執行!")
}
setTimeout(later,1000)
function now(){
return 21
}
var answer=now()
console.log("現在就執行:",answer)
由此可以看出:異步機制的必要條件有,(1)將來執行的函數,(2)觸發將來執行函數的事件
- 怎樣管理異步?
//如果不能有效控制異步編程機制,有可能在代碼編寫時出現出乎意料的結果
//比如:對於瀏覽器的異步控制檯I/O,就是異步輸出
var a={
index:1
}
console.log(a)
a.index++
//一般情況下,會先輸出{index:1},在增1,但是如果在控制檯異步輸出延遲下,就會出現輸出{index:2}
//所以必須對異步機制進行有效的管理,防止出現不可控的結果!!
注:
JavaScript引擎不是獨立運行的,必須運行在宿主環境中,如瀏覽器,node.js,JavaScript引擎本身在需要
的時候,在給定的任意時刻解釋執行程序的單個代碼塊,儘管運行的環境不盡相同,但是都有一個共同的點,就是
都提供一種機制來處理程序中多個塊的執行,每次執行塊是則調用JavaScript引擎,被稱爲事件循環
記住一點:JavaScript引擎只負責在執行代碼塊時解釋執行,但其本身沒有時間概念,事件(代碼的執行)調度都是由宿主環境進行的!!
**舉例:**JavaScript程序發出一個Ajax請求,設置好響應後的執行函數(通常爲回調函數),之後JavaScript引擎就會通
知宿主環境(即瀏覽器或者node.js),瀏覽器就會設置監聽網絡的響應,一旦拿到響應的數據,就會將回調函數插入
到事件循環機制中,並調用JavaScript引擎執行!!
事件循環機制:
//使用僞代碼進行模擬事件循環機制
var eventLoop=[]
var curEvent
while(ture){
if(eventLoop.length>0){
curEvent=eventLoop.shift()
}
try{
event()//這些事件就是你的回調函數!!
}catch(err){
reportError(err)
}
}
//setTimeout並沒有將你的回調函數加入事件循環隊列中,它只是設置一個定時器的作用,當定時到了之後,
//宿主環境會將回調函數加入到事件循環隊列中,如果在它之前還有其他事件在等待,就只能等待前面項目執行完
//也就是說setTimeout定時器不會嚴格精度執行回調函數,這與當前事件隊列狀態而定!!
//在ES6中引入Promise,從而使JavaScript引擎開始接管事件循環的控制,而不是宿主環境,也就是說使用
//Promise能夠精確控制事件循環的調度執行!!
0.3 異步對比並行機制和併發機制
/*
1.異步是關於現在和未來的時間間隙,而並行指能夠同時發生的事情
2.並行計算的機制通常使用進程和線程,二者能夠同時運行,每個線程都有各自的調用棧,但是共享同一個
進程的內存數據
而與之相對的異步執行的事件循環機制,是將當前的工作分成一個個獨立的任務順序執行,能夠訪問共享內存,
但是不能夠同時訪問,,但是如果將事件循環分配給多個線程並行執行,則可以同時共享內存!!!
3.如果是多個線程執行事件循環,有可能對那些有順序依賴的事件出現不同的執行結果!!所以必須通過某
中機制防止多個線程的交錯和中斷執行,保證執行的確定、準確性!!!
由此看出:調用多個線程雖然能夠處理更多的任務,但是也會出現不確定結果的不良後果!!!
4.JavaScript具有單線程運行的特點,所以代碼具有原子性,沒有多線程執行的詬病!!
但是對於多個異步執行塊,由於都不確定它的執行時間,也就無法確定誰先執行,但是這種不確定性
只是建立在事件函數級別上,而不是多線程執行的順序級別,
換而言之,JavaScript儘管爲單線程機制,但是由於事件循環的不確定性,也會出現代碼塊執行的
競爭狀態,看誰會被先執行!!!於是引入了生成器,解決這一個缺點!!
*/
對比並行和併發機制:併發是在進程任務級的同時執行,而並行爲線程級的運算處理級的同時執行!!
/*
假想:不斷下拉一個網站列表,會逐漸加載更多的內容,其中至少需要兩個“進程”同時運行
第一個進程不斷觸發onscroll事件的請求函數
第二個進程則接受Ajax的響應數據,並將數據加載到頁面中,
隨着下拉速度的加快,就會出現多個請求的併發進行,同時出現多個併發的Ajax響應接收運行!!
也就是說,併發是在進程任務級的同時執行,而並行爲線程級的運算處理級的同時執行!!
不同進程之間也能夠進行交互,如果不同進程不相關則不會發生交互,相關可能會發生交互!!
一旦發生交互,就需要對他們之間的交互進行協調,避免競爭狀態的發生,修改回調函數代碼!!
但是不同於線程共享內存數據!!
*/
var res=[]
function response(data){
res.push(data)
}
ajax("url1",response)
ajax("url2",response)//res的響應不確定,出現競爭態
var res=[]
function response(data){
if(data.url=="url1")
res[0]=data
else if(data.url=="url2")
res[1]=data
}
ajax("url1",response)
ajax("url2",response)//通過修改回調函數,從而協調了併發執行出現的競爭態!
//通過協作的方法:如果當前處理的事件需要消耗大量的時間,不可能一直等待,後面還有一大堆的事件等待執行
//這時必須通過協作的方法,另外開闢一個線程,如果後面的事件不依賴後面的事件,則直接處理後面的事件
//,如果依賴,則線程協同一起處理當前的大事件,從而避免了一直等待!!
0.4 對比事件循環機制和任務循環機制
-
**事件循環機制:**必須等待前面的事件執行完才能夠執行
-
**任務循環機制:**表示如果執行過此任務,還需要執行,則可以插隊執行,而不需要等待前面任務的執行
記住Promise是基於任務循環機制的!!!!!!!
0.4 記住在JavaScript中,語句的執行順序與js引擎的執行順序不一定一致,所以對代碼的調試相比其他語言困難
1.異步的基礎------異步模式的主力軍回調函數
/*
1.回調函數的缺點:對於適應順序執行的大腦而言,異步執行的方式會使js代碼難以跟蹤,調試
2.更爲主要的是對於多個回調函數,由於需要對函數輸入進行安全類型檢查,使得代碼的維護性差
此外,由於回調函數的異步依賴性,想要更新修改某個函數,就必須全部整體的進行調整,
可謂真正的回調地獄!!!
3.爲了解決信任問題,可以使用API分離回調函數,一個用於執行成功時候的回調,一個用於執行失敗時候的
回調
*/
//設置成對的回調
function success(data){
...
}
function failure(data){
...
}
ajax("url",success,failure)
//設置超時取消
function timeoutify(fn,delay){
var inde=setTimeout(function(){
inde=null
fn(new Error("timeout"))
},delay)
return function(){
if(inde){//沒有超時
clearTimeout(inde)
fn.apply(this.arguments)
}
}
}
ajax("url",timeoutify(foo,1000))
注:總之回調函數實現異步主要有兩大缺陷,缺乏順序性和可信任性