javascript
for循环里面有异步操作_JS 线程与异步的那些事
已知,JavaScript 是單線程的,天生異步,適合 IO 密集型,不適合 CPU 密集型,但是,為什么是異步的喃,異步由何而來的喃,我們將在這里逐漸討論實現。
一、進程與線程
1. 瀏覽器是多進程的
它主要包括以下進程:
Browser 進程:瀏覽器的主進程,唯一,負責創建和銷毀其它進程、網絡資源的下載與管理、瀏覽器界面的展示、前進后退等。
GPU 進程:用于 3D 繪制等,最多一個。
第三方插件進程:每種類型的插件對應一個進程,僅當使用該插件時才創建。
瀏覽器渲染進程(瀏覽器內核):內部是多線程的,每打開一個新網頁就會創建一個進程,主要用于頁面渲染,腳本執行,事件處理等。
2. 渲染進程(瀏覽器內核)
瀏覽器的渲染進程是多線程的,頁面的渲染,JavaScript 的執行,事件的循環,都在這個進程內進行:
GUI 渲染線程:負責渲染瀏覽器界面,當界面需要重繪(Repaint)或由于某種操作引發回流(Reflow)時,該線程就會執行。
JavaScript 引擎線程:也稱為 JavaScript 內核,負責處理 Javascript 腳本程序、解析 Javascript 腳本、運行代碼等。(例如 V8 引擎)
事件觸發線程:用來控制瀏覽器事件循環,注意這不歸 JavaScript 引擎線程管,當事件被觸發時,該線程會把事件添加到待處理隊列的隊尾,等待 JavaScript 引擎的處理。
定時觸發器線程:傳說中的 setInterval 與 setTimeout 所在線程,注意,W3C 在 HTML 標準中規定,規定要求 setTimeout 中低于 4ms 的時間間隔算為 4ms 。
異步 http 請求線程:在 XMLHttpRequest 連接后通過瀏覽器新開一個線程請求,將檢測到狀態變更時,如果設置有回調函數,異步線程就產生狀態變更事件,將這個回調再放入事件隊列中。再由 JavaScript 引擎執行。
注意,GUI 渲染線程與 JavaScript 引擎線程是互斥的,當 JavaScript 引擎執行時 GUI 線程會被掛起(相當于被凍結了),GUI 更新會被保存在一個隊列中等到 JavaScript 引擎空閑時立即被執行。所以如果 JavaScript 執行的時間過長,這樣就會造成頁面的渲染不連貫,導致頁面渲染加載阻塞。
二、單線程的 JavaScript
所謂單線程,是指在 JavaScript 引擎中負責解釋和執行 JavaScript 代碼的線程唯一,同一時間上只能執行一件任務。
問題:首先為什么要引入單線程喃?
我們知道:
瀏覽器需要渲染 DOM
JavaScript 可以修改 DOM 結構
JavaScript 執行時,瀏覽器 DOM 渲染停止
如果 JavaScript 引擎線程不是單線程的,那么可以同時執行多段 JavaScript,如果這多段 JavaScript 都修改 DOM,那么就會出現 DOM 沖突。
你可能會說,web worker 就支持多線程,但是 web worker 不能訪問 window 對象,document 對象等。
原因:避免 DOM 渲染的沖突
當然,我們可以為瀏覽器引入鎖 的機制來解決這些沖突,但其大大提高了復雜性,所以 JavaScript從誕生開始就選擇了單線程執行。
引入單線程就意味著,所有任務需要排隊,前一個任務結束,才會執行后一個任務。這同時又導致了一個問題:如果前一個任務耗時很長,后一個任務就不得不一直等著。
//?實例1let?i,?sum?=?0
for(i?=?0;?i?1000000000;?i?++)?{
????sum?+=?i
}
console.log(sum)
在實例1中,sum 并不能立刻打印出來,必須在 for 循環執行完成之后才能執行 console.log(sum) 。
//?實例2console.log(1)
alert('hello')
console.log(2)
在實例2中,瀏覽器先打印 1 ,然后彈出彈框,點擊確定后才執行 console.log(2) 。
總結:
優點:實現比較簡單,執行環境相對單純
缺點:只要有一個任務耗時很長,后面的任務都必須排隊等著,會拖延整個程序的執行。常見的瀏覽器無響應(假死),往往就是因為某一段 Javascript 代碼長時間運行(比如死循環),導致整個頁面卡在這個地方,其他任務無法執行。
為了解決這個問題,JavaScript 語言將任務的執行模式分為兩種:同步和異步
三、同步與異步
1. 同步
func(args...)如果在函數 func 返回的時候,調用者就能夠得到預期結果(即拿到了預期的返回值或者看到了預期的效果),那么這個函數就是同步的。
let?a?=?1Math.floor(a)
console.log(a)?//?1
2. 異步
如果在函數 func 返回的時候,調用者還不能夠得到預期結果,而是需要在將來通過一定的手段得到,那么這個函數就是異步的。
fs.readFile('foo.txt',?'utf8',?function(err,?data)?{????console.log(data);
});
總結:
JavaScript 采用異步編程原因有兩點,
一是 JavaScript 是單線程;
二是為了提高 CPU 的利用率。
四、異步過程
fs.readFile('data.json',?'utf8',?function(err,?data)?{????console.log(data)
})
在執行這段代碼時,fs.readFile 函數返回時,并不會立刻打印 data ,只有 data.json 讀取完成時才打印。也就是異步函數 fs.readFile 執行很快,但后面還有工作線程執行異步任務、通知主線程、主線程回調等操作,這個過程就叫做異步過程。
主線程發起一個異步操作,相應的工作線程接受請求并告知主線程已收到(異步函數返回);主線程繼續執行后面的任務,同時工作線程執行異步任務;工作線程完成任務后,通知主線程;主線程收到通知后,執行一定的動作(調用回調函數)。
工作線程在異步操作完成后通知主線程,那么這個通知機制又是如何顯現喃?答案就是就是消息隊列與事件循環。
五、消息隊列與事件循環
工作線程將消息放在消息隊列,主線程通過事件循環過程去取消息。
消息隊列:消息隊列是一個先進先出的隊列,它里面存放著各種消息。
事件循環:事件循環是指主線程重復從消息隊列中取消息、執行的過程。
1. 事件循環(eventloop)
主線程不斷的從消息隊列中取消息,執行消息,這個過程稱為事件循環,這種機制叫事件循環機制,取一次消息并執行的過程叫一次循環。
大致實現過程如下:
while(true)?{????var?message?=?queue.get()
????execute(message)
}
例如:
$.ajax({????url:?'xxxx',
????success:?function(result)?{
????????console.log(1)
????}
})
setTimeout(function()?{
????console.log(2)
},?100)
setTimeout(function()?{
????console.log(3)
})
console.log(4)
// output:4321 或 4312
其中,主線程:
//?主線程console.log(4)
異步隊列:
//?異步隊列function?()?{
????console.log(3)
}
function?()?{?//?100ms后
????console.log(2)
}
function()?{?//?ajax加載完成之后
????console.log(1)
}
事件循環是JavaScript實現異步的具體解決方案,其中同步代碼,直接執行;異步函數先放在異步隊列中,待同步函數執行完畢后,輪詢執行 異步隊列 的回調函數。
2. 消息隊列
其中,消息就是注冊異步任務時添加的回調函數。
$.ajax('XXX',?function(res)?{????console.log(res)
})
...
主線程在發起 AJAX 請求后,會繼續執行其他代碼,AJAX 線程負責請求 XXX,拿到請求后,會封裝成 JavaScript 對象,然后構造一條消息:
//?消息隊列里的消息var?message?=?function?()?{
????callback(response)
}
其中 callback 是 AJAX 網絡請求成功響應時的回調函數。
主線程在執行完當前循環中的所有代碼后,就會到消息隊列取出這條消息(也就是 message 函數),并執行它。到此為止,就完成了工作線程對主線程的 通知 ,回調函數也就得到了執行。如果一開始主線程就沒有提供回調函數,AJAX 線程在收到 HTTP 響應后,也就沒必要通知主線程,從而也沒必要往消息隊列放消息。
異步過程中的回調函數,一定不在當前這一輪事件循環中執行。
六、異步與事件
消息隊列中的每條消息實際上都對應著一個事件。
其中一個重要的異步過程就是:DOM事件
var?button?=?document.getElementById('button')button.addEventLister('click',?function(e)?{
????console.log('事件')
})
從異步的角度看,addEventLister 函數就是異步過程的發起函數,事件監聽器函數就是異步過程的回調函數。事件觸發時,表示異步任務完成,會將事件監聽器函數封裝成一條消息放在消息隊列中,等待主線程執行。
事件的概念實際上并不是必須的,事件機制實際上就是異步過程的通知機制。
另外,所有的異步過程也都可以用事件來描述。例如:
setTimeout(func,?1000)//?可以看成:
timer.addEventLister('timeout',?1000,?func)
其中關于事件的詳細描述,可以看這篇文章:事件綁定、事件監聽、事件委托,這里不再深入介紹。
七、生產者與消費者
生產者和消費者問題是線程模型中的經典問題:生產者和消費者在同一時間段內共用同一個存儲空間,生產者往存儲空間中添加數據,消費者從存儲空間中取走數據,當存儲空間為空時,消費者阻塞,當存儲空間滿時,生產者阻塞。
從生產者與消費者的角度看,異步過程是這樣的:
工作線程是生產者,主線程是消費者(只有一個消費者)。工作線程執行異步任務,執行完成后把對應的回調函數封裝成一條消息放到消息隊列中;主線程不斷地從消息隊列中取消息并執行,當消息隊列空時主線程阻塞,直到消息隊列再次非空。
那么異步的實現方式有哪些喃?
ES6之前:callback、eventloop、Promise
ES6:Generator
ES7:Async/Await
八、走在最后
1.???玩得開心,不斷學習,并始終保持編程。?
2.??點擊原文,查看更多精彩文章!?
3. 如有任何問題或更獨特的見解,歡迎聯系瓶子君!(掃碼關注公眾號,回復 123 即可)??
我知道你 “在看”
總結
以上是生活随笔為你收集整理的for循环里面有异步操作_JS 线程与异步的那些事的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 深入浅出强化学习_直播 | 深入浅出理解
- 下一篇: php artisan 计划任务,Lar