JavaScript執行順序分析
前言
被問到了事件執行順序的問題,想起來之前看《深入淺出Node.js》時看到這一章就忽略了,這次來分析一下JavaScript的事件執行順序。廢話少說,正題開始。
單執行緒JavaScript
首先我們要知道JavaScript
是一門單執行緒解釋型語言。這就意味著在同一個時間下,我們只能執行一條命令。之所以它是一門單執行緒語言,和它的用途有關。
JavaScript
設計出來的初衷是為了增強瀏覽器與使用者的互動,尤其是表單的互動,而之後的Ajax
技術也是為了使表單的互動更加人性化而發明出來的。因為JavaScript
是一門解釋型的語言,而直譯器內嵌於瀏覽器,這個直譯器是單執行緒的。
之所以不設計成多執行緒是因為渲染網頁的時候多執行緒容易引起死鎖或者資源衝突等問題。但是瀏覽器本身是多執行緒的,比如解釋執行JavaScript
的同時還在載入網路資源。
Why doesn’t JavaScript support multithreading?
事件迴圈
單執行緒就意味著如果你要執行很多命令,那麼這些命令需要排序,一般情況下,這些命令是從上到下排序執行(因為直譯器是從檔案頂部開始)。比如以下程式碼是按照順序執行的。
console.log("1");
console.log("2");
console.log("3");
//1
//2
//3
但是我們還有知道在JavaScript
裡有非同步程式設計的說法,比如Ajax
,setTimeout
,setInterval
或者ES6
中的Promise
,async
,await
。
那麼什麼是同步和非同步呢?
一條命令的執行在計算機裡的意思就是它此時在使用CPU等資源,那麼因為想要獲得CPU資源的命令有很多,而CPU執行命令也需要時間去運算獲得結果,於是就有了同步非同步的概念。
同步就是在發出一個CPU請求時,在沒有得到結果之前,該CPU請求就不返回。但是一旦呼叫返回,就得到返回值了。
非同步表示CPU請求在發出之後,這個呼叫就直接返回了,所以沒有返回結果。在執行結束後,需要通過一系列手段來獲得返回值
這時候就要引入程式和執行緒的概念。
程式與執行緒
程式
概念:程式是一個具有一定獨立功能的程式在一個資料集上的一次動態執行的過程,是作業系統進行資源分配和排程的一個獨立單位,是應用程式執行的載體。
執行緒
由於程式對於CPU的使用是輪流的,那麼就存在程式的切換,但是由於現在的程式都比較大,切換的開銷很大會浪費CPU的資源,於是就發明了執行緒,把一個大的程式分解成多個執行緒共同執行。
區別
- 程式是作業系統分配資源的最小單位,執行緒是程式執行的最小單位。
- 一個程式由一個或多個執行緒組成,執行緒是一個程式中程式碼的不同執行路線;
- 程式之間相互獨立,但同一程式下的各個執行緒之間共享程式的記憶體空間(包括程式碼段、資料集、堆等)及一些程式級的資源(如開啟檔案和訊號)。
- 排程和切換:執行緒上下文切換比程式上下文切換要快得多。
舉個例子
假如我是鳴人,我想吃很多拉麵,如果我一個人吃10碗的話,那我就是一個程式一個執行緒完成吃拉麵這件事情。
但是如果我用9個分身和我一起吃10碗拉麵,那我就是一個程式用9個執行緒去完成吃拉麵這件事情。
而多程式這表示名人在一樂拉麵裡面吃拉麵的同時,好色仙人在偷看妹子洗澡~ ~。好色仙人是單程式單執行緒去偷看的哦!
瀏覽器的執行緒
瀏覽器的核心是多執行緒的,在核心控制下各執行緒相互配合以保持同步,一個瀏覽器通常由一下執行緒組成:
- GUI 渲染執行緒
- JavaScript引擎執行緒
- 事件觸發執行緒
- 非同步http請求執行緒
EventLoop
輪詢的處理執行緒
這些執行緒的作用:
- UI執行緒用於渲染頁面
- js執行緒用於執行js任務
- 瀏覽器事件觸發執行緒用於控制互動,響應使用者
- http執行緒用於處理請求,ajax是委託給瀏覽器新開一個http執行緒
EventLoop
處理執行緒用於輪詢訊息佇列
JavaScript
事件迴圈和訊息佇列(瀏覽器環境)
因為JavaScript
是單執行緒的,而瀏覽器是多執行緒的,所以為了執行不同的同步非同步的程式碼,JavaScript
執行的環境採用裡事件迴圈和訊息佇列來達到目的。
每個執行緒的任務執行順序都是FIFO(先進先出)
在JavaScript
執行的環境中,有一個負責程式本身的執行,作為主執行緒;另一個負責主執行緒與其他執行緒的通訊,被稱為Event Loop 執行緒。
每當主執行緒遇到非同步的任務,把他們移入到Event Loop 執行緒,然後主執行緒繼續執行,等到主執行緒完全執行完之後,再去Event Loop 執行緒拿結果。
而每個非同步任務都包含著與它相關聯的資訊,比如執行狀態,回撥函式等。
由此我們可以知道,同步任務和非同步任務會被分發到不同的執行緒去執行。
現在我們就可以分析一下一下程式碼的執行結果了。
setTimeout(()=>{console.log("我才是第一");},0);
console.log("我是第一");
- 因為
setTimeout
是非同步的事件,所以主執行緒把它調入Event Loop執行緒進行註冊。 - 主執行緒繼續執行
console.log("我是第一");
- 主執行緒執行完畢,從Event Loop 執行緒讀取回撥函式。再執行
console.log("我才是第一");;
setTimeout
和 setInterval
setTimeout
這裡值得一提的是,setTimeout(callback,0)
指的是主執行緒中的同步任務執行完了之後立刻由Event Loop 執行緒調入主執行緒。
而計時是在調入Event Loop執行緒註冊時開始的,此時setTimeout
的回撥函式執行時間與主執行緒執行結束的時間相關。
關於setTimeout
要補充的是,即便主執行緒為空,0毫秒實際上也是達不到的。根據HTML的標準,最低是4毫秒。
setInterval
需要注意的是,此函式是每隔一段時間將回撥函式放入Event Loop執行緒。
一旦setInterval
的回撥函式fn執行時間超過了延遲時間ms,那麼就完全看不出來有時間間隔了
micro-task(微任務)
與 macro-task(巨集任務)
Event Loop執行緒中包含任務佇列(用來對不同優先順序的非同步事件進行排序),而任務佇列又分為macro-task(巨集任務)與micro-task(微任務),在最新標準中,它們被分別稱為task
與jobs
。
macro-task
大概包括:script(整體程式碼),setTimeout
,setInterval
,setImmediate
, I/O, UI rendering。micro-task
大概包括:process.nextTick
,Promise
,Object.observe
(已廢棄),
MutationObserver
(html5新特性)setTimeout/Promise
等我們稱之為任務源。而進入任務佇列的是他們指定的具體執行任務(回撥函式)。
來自不同的任務源的任務會進入到不同的任務佇列中,而不同的任務佇列執行過程如下:
執行過程如下:
JavaScript引擎首先從macro-task
中取出第一個任務,
執行完畢後,將micro-task
中的所有任務取出,按順序全部執行;
然後再從macro-task
中取下一個,
執行完畢後,再次將micro-task
中的全部取出;
迴圈往復,直到兩個佇列中的任務都取完。
舉個大例子
console.log("start");
var promise = new Promise((resolve) => {
console.log("promise start..");
resolve("promise");
}); //3
promise.then((val) => console.log(val));
setTimeout(()=>{console.log("setTime1")},0);
console.log("test end...")
這裡我們按順序來分析。
第一輪
- 整體script程式碼作為一個巨集任務進入主執行緒,執行
console.log("start");
。 - 然後遇到
Promises
直接執行console.log("promise start..")
。 - 然後遇到
promise.then
,存入到micro-task
佇列中。 - 然後遇到
setTimeout
,存入到macro-task
佇列中。 - 於然後執行
console.log("test end...")
; - 在這一輪中,巨集任務執行結束,執行
micro-task
佇列中的promise.then
,輸出promise
第二輪
- 取出
macro-task
佇列中的setTimeout
,執行console.log("setTime1")
;
結果
輸出的順序就是
// start
// promise start
// test end...
// promise
//setTime1
留一個案例你們去分析
async function testSometing() {
console.log("執行testSometing");
return "testSometing";
}
async function testAsync() {
console.log("執行testAsync");
return Promise.resolve("hello async");
}
async function test() {
console.log("test start...");
const v1 = await testSometing();
console.log(v1);
const v2 = await testAsync();
console.log(v2);
console.log(v1, v2);
}
test();
var promise = new Promise((resolve) => {
console.log("promise start..");
resolve("promise");
}); //3
promise.then((val) => console.log(val));
setTimeout(()=>{console.log("setTime1")},3000);
console.log("test end...")
相關文章
- Sql執行順序SQL
- JavaScript程式碼執行順序和資料型別JavaScript資料型別
- pipeline的執行順序
- mySQL 執行語句執行順序MySql
- SQL語句執行順序SQL
- Select語句執行順序
- js執行順序Event LoopJSOOP
- Spring Aop的執行順序Spring
- sql mysql 執行順序 (4)MySql
- 【JavaScript】JS引擎中執行上下文如何順序執行程式碼JavaScriptJS行程
- Pytest 順序執行,依賴執行,引數化執行
- async await、Promise、setTimeout執行順序AIPromise
- js解惑-函式執行順序JS函式
- [20191215]seq控制執行順序.txt
- SQL 語句的執行順序SQL
- mysql 語句的執行順序MySql
- 關於 Promise 的執行順序Promise
- sql select語法執行順序SQL
- 聊聊如何讓springboot攔截器的執行順序按我們想要的順序執行Spring Boot
- join、volatile、newSingleThreadLatch 實現執行緒順序執行thread執行緒
- Java中如何保證執行緒順序執行Java執行緒
- [20191112]flock控制命令執行順序.txt
- unittest--TestCase 按宣告順序執行
- Go包中程式碼執行順序Go
- kafka多執行緒順序消費Kafka執行緒
- 路由的中介軟體執行順序路由
- 利用訊號量實現執行緒順序執行執行緒
- 【高併發】深入理解執行緒的執行順序執行緒
- Python執行緒專題10:queue、多執行緒按順序執行Python執行緒
- 令人費解的 async/await 執行順序AI
- SQL語句各子句的執行順序SQL
- Java之執行緒的優先順序Java執行緒
- C#中Page執行順序:OnPreInit()、OnInit()……C#
- Jmeter的元件作用域和執行順序JMeter元件
- Gradle系列(二) Gradle執行順序和taskGradle
- java繼承關係下執行順序Java繼承
- pytest(4)-測試用例執行順序
- C#類中方法的執行順序C#