事件循環
//輸出結果是多少呢,先輸出'abc'還是先交叉輸出
//還是先輸出i呢
setTimeout(function func1() {
console.log("abc")
}, 0);
for (var i = 0; i < 10000; i++) {
console.log(i);
}
概念
ps:最後有面試題哦
- 瀏覽器、JS、執行引擎的關係
JS:一門計算機語言,提供了表達程序邏輯的語法和實現基本功能的API
瀏覽器:JS語言的真實運行環境,又稱之爲JS的宿主環境
JS執行引擎:JS宿主環境(例如瀏覽器)中的一個功能模塊,用於解析並執行JS
它們的關係如下:
- 進程 和 線程
進程:當一個應用程序運行時,需要使用內存和CPU資源,這些資源需要向操作系統申請。操作系統以進程的方式來分配這些資源,一個進程就代表着一塊獨立於其他進程的內存空間。一個應用程序要運行,必須至少有一個進程啓動。進程的最大特點是獨立,一個進程不能隨意的訪問其他進程的資源。這就保證了多個程序在操作系統上運行互不干擾。
線程:可能要同時執行多個任務,每個任務需要在一個線程上運行,線程與線程之間相對獨立,但可以共享應用程序的進程數據。
- 如何理解JS的單線程
我們之所以稱JS爲單線程的語言,是因爲它的執行引擎只有一個線程,並且不會在執行期間開啓新的線程。而並非瀏覽器是單線程的。
單線程的應用程序具有以下的特點:
- 易於學習和理解:所有代碼都是按照順序從上到下執行的
- 易於掌控程序:由於代碼都按照順序執行,不會出現中斷,也沒有共享資源的爭奪問題,極大的降低了開發難度。
- 更加合理的利用計算機資源:創建新的線程和銷燬線程都會耗費額外的CPU和內存資源,沒有良好的線程設計,將導致程序運行效率低下。而單線程的應用不受此影響
- JS如何處理多任務
任何一個程序在執行期間都可能會開啓多個任務,比如:
1). 任務1:程序啓動時開始進行一些操作
2). 任務2:開啓一個計時器,每隔一段時間去做一些事
3). 任務3:監聽按鈕是否被點擊,當按鈕被點擊後,去做一些事
由於JS的執行線程只有一個,因此,它通過異步的方式來解決這些任務
下面是單線程的異步和多線程的對比
多線程:
可以看到,如果以多線程的方式運行,會導致程序代碼在某些時候會有重疊執行的情況出現,如果這些代碼湊巧在使用共享數據,將難以控制最終的運行結果。
而使用異步的方式就解決了該問題:
可以看出,使用異步之後,所有的JS代碼都在單個線程中執行,不會出現多個任務同時執行的情況,自然就不會出現資源爭奪的問題。
- 同步代碼 和 異步代碼
同步代碼:程序啓動後,在JS執行線程上立即執行的任務代碼
異步代碼:收到宿主環境(瀏覽器)的其它線程通知,即將在JS執行線程上執行的代碼,例如計時器回調函數中的代碼,事件中的代碼。JS中的異步代碼往往放到一個函數中,該函數成爲異步函數,或者描述爲,該函數是異步的
- 執行棧
爲了保證JS代碼有序的執行,JS執行引擎使用執行棧來組織JS代碼
每當調用一個函數時,都會在執行棧中創建一個執行上下文,上下文中提供了函數執行需要的環境,創建了上下文之後,再執行函數
事件循環
事件循環是JS處理異步函數的具體方法
具體的做法是:
- 執行 執行棧 中的代碼
- 遇到一些特殊代碼交給瀏覽器的其他線程處理
- 將執行棧中的代碼全部執行完畢
- 從事件隊列中取出第一個任務放入執行棧,然後重複第1步
事件隊列在不同的宿主環境中有所差異,大部分宿主環境會將事件隊列進行細分。在瀏覽器中,事件隊列分爲兩種:
- 宏任務(隊列):macroTask,計時器結束的回調、事件回調、http回調等等絕大部分異步函數進入宏隊列
- 微任務(隊列):microTask,Promise.then, MutationObserver
當執行棧清空時,JS引擎首先會將微任務中的所有任務依次執行結束,如果沒有微任務,則執行宏任務。
如果上面文章都懂了,那麼來道題目開開胃吧!
這道題目要好好的思考一下,在這裏就不公佈答案了,可以自己運行一下看看答案正確與否
setTimeout(function func1() {
console.log(1)
a();
}, 0)
function a() {
setTimeout(function func2() {
console.log(2)
}, 0)
console.log(3)
}
a();
console.log(4)