JS三座大山再學習 ---- 異步和單線程

本文已發佈在西瓜君的我的博客,原文傳送門javascript

前言

寫這一篇的時候,西瓜君查閱了不少資料和文章,可是至關多的文章寫的都很簡單,甚至互相之間有矛盾,這讓我很困擾;同時也讓我堅決了要寫出一篇好的關於JS異步、單線程、事件循環的文章,下面,讓咱們一塊兒來學習本文吧,衝鴨~~html

單線程

### 1. 什麼是單線程html5

//栗子1
console.log(1)
console.log(2)
console.log(3)
//輸出順序 1 2 3

單線程即同一時間只作一件事java

2. JavaScript爲何是單線程

  • 首先是歷史緣由,在建立 javascript 這門語言時,多進程多線程的架構並不流行,硬件支持並很差。
  • 其次是由於多線程的複雜性,多線程操做須要加鎖,編碼的複雜性會增高。
  • 並且,若是同時操做 DOM ,在多線程不加鎖的狀況下,最終會致使 DOM 渲染的結果不可預期

爲了利用多核CPU的計算能力,HTML5提出Web Worker標準,容許JavaScript腳本建立多個線程,可是子線程徹底受主線程控制,且不得操做DOM。因此,這個新標準並無改變JavaScript單線程的本質。node

異步

1.JS的 同步任務/異步任務

  • 同步任務:在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務web

  • 異步:不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務能夠執行了,該任務纔會進入主線程執行chrome

//異步的栗子
console.log(1)

setTimeout(()=>{
    console.log(2)
},100)

console.log(3)
//輸出順序 1 3 2

2. JavaScript爲何須要異步

若是在JS代碼執行過程當中,某段代碼執行太久,後面的代碼遲遲不能執行,產生阻塞(即卡死),會影響用戶體驗。api

JavaScript怎麼實現異步

JS 實現異步時經過 事件循環(Event Loop),下面咱們來了解一下瀏覽器

1.執行棧與任務隊列

先理解幾個概念markdown

  • JS任務 分爲同步任務(synchronous)和異步任務(asynchronous)
  • 同步任務都在 JS引擎線程(主線程) 上執行,造成一個執行棧(call stack)
  • 事件觸發線程 管理一個 任務隊列(Task Queue)
  • 異步任務 觸發條件達成,將 回調事件 放到任務隊列(Task Queue)中
  • 執行棧中全部同步任務執行完畢,此時JS引擎線程空閒,系統會讀取任務隊列,將可運行的異步任務回調事件添加到執行棧中,開始執行

當一個JS文件第一次執行的時候,js引擎會 解析這段代碼,並將其中的同步代碼 按照執行順序加入執行棧中,而後從頭開始執行。若是當前執行的是一個方法,那麼js會向執行棧中添加這個方法的執行環境,而後進入這個執行環境繼續執行其中的代碼。當這個執行環境中的代碼 執行完畢並返回結果後,js會退出這個執行環境並把這個執行環境銷燬,回到上一個方法的執行環境。這個過程反覆進行,直到執行棧中的代碼所有執行完畢。

舉個栗子:

//Event loop

//(1)
console.log(1)

//(2)
setTimeout(()=>{
    console.log(2)
},100)

//(3)
console.log(3)
  1. 先解析整段代碼,按照順序加入到執行棧中,從頭開始執行
  2. 先執行(1),是同步的,因此直接打印 1
  3. 執行(2),發現是 setTimeout,因而調用瀏覽器的方法(webApi)執行,在 100ms後將 console.log(2) 加入到任務隊列
  4. 執行(3),同步的,直接打印 3
  5. 執行棧已經清空了,如今檢查任務隊列,(執行太快的話可能此時任務隊列仍是空的,沒到100ms,尚未將(2)的打印加到任務隊列,因而不停的檢測,直到隊列中有任務),發現有 console.log(2),因而添加到執行棧,執行console.log(2),同步代碼,直接打印 2 (若是這裏是異步任務,一樣會再走一遍循環:-->任務隊列->執行棧)

因此結果是 1 3 2

注:setTimeout/Promise等咱們稱之爲任務源。而進入任務隊列的是他們指定的回調

2.宏任務(macro task)與微任務(micro task)

上面的循環只是一個宏觀的表述,實際上異步任務之間也是有不一樣的,分爲 宏任務(macro task) 與 微任務(micro task),最新的標準中,他們被稱爲 taskjobs

  • 宏任務有哪些:script(總體代碼), setTimeout, setInterval, setImmediate, I/O, UI rendering(渲染)
  • 微任務有哪些:process.nextTick, Promise, Object.observe(已廢棄), MutationObserver(html5新特性)

下面咱們再詳細講解一下執行過程

執行棧在執行的時候,會把宏任務放在一個宏任務的任務隊列,把微任務放在一個微任務的任務隊列,在當前執行棧爲空的時候,主線程會 查看微任務隊列是否有事件存在。若是微任務隊列不存在,那麼會去宏任務隊列中 取出一個任務 加入當前執行棧;若是微任務隊列存在,則會依次執行微任務隊列中的全部任務,直到微任務隊列爲空(一樣,是吧隊列中的事件加到執行棧執行),而後去宏任務隊列中取出最前面的一個事件加入當前執行棧...如此反覆,進入循環。

注:

  • 宏任務和微任務的任務隊列均可以有多個
  • 當前執行棧執行完畢時會馬上先處理全部微任務隊列中的事件,而後再去宏任務隊列中取出一個事件。同一次事件循環中,微任務永遠在宏任務以前執行。
  • 不一樣的運行環境 循環策略可能有不一樣,這裏探討chrome、node環境

舉個栗子:

//Event loop


//(1)
setTimeout(()=>{
    console.log(1)
},100)

//(2)
setTimeout(()=>{
    console.log(2)
},100)

//(3)
new Promise(function(resolve,reject){
    //(4)
    console.log(3)
    resolve(4)
}).then(function(val){
    //(5)
    console.log(val);
})

//(6)
new Promise(function(resolve,reject){
    //(7)
    console.log(5)
    resolve(6)
}).then(function(val){
    //(8)
    console.log(val);
})

//(9)
console.log(7)

//(10)
setTimeout(()=>{
    console.log(8)
},50)

*上面的代碼在node和chrome環境的正確打印順序是 3 5 7 4 6 8 1 2

下面分析一下執行過程:

  1. 所有代碼在解析後加入執行棧
  2. 執行(1),宏任務,調用webapi setTimeout,這個方法會在100ms後將回調函數放入宏任務的任務隊列
  3. 執行(2),同(1),可是會比(1)稍後一點
  4. 執行(3),同步執行new Promise,而後執行(4),直接打印 3 ,而後resolve(4),而後.then(),把(5)放入微任務的任務隊列
  5. 執行(6),同上,先打印 5 ,再執行resolve(6),而後.then()裏面的內容(8)加入到微任務的任務隊列
  6. 執行(9),同步代碼,直接打印 7
  7. 執行(10),同(1)和(2),只是時間更短,會在 50ms 後將回調 console.log(8) 加入宏任務的任務隊列
  8. 如今執行棧清空了,開始檢查微任務隊列,發現(5),加入到執行棧執行,是同步代碼,直接打印 4
  9. 任務隊列又執行完了,又檢查微任務隊列,發現(8),打印 6
  10. 任務隊列又執行完了,檢查微任務隊列,沒有任務,再檢查宏任務隊列,此時若是超過了50ms的話,會發現 console.log(8) 在宏任務隊列中,因而執行 打印 8
  11. 依次打印 1 2

注:由於渲染也是宏任務,須要在一次執行棧執行完後纔會執行渲染,因此若是執行棧中同時有幾個同步的改變同一個樣式的代碼,在渲染時只會渲染最後一個

結語

寫到這裏,仍然以爲還有不少知識點沒有寫出來,可是想寫又不知道從哪裏入手。因而決定今天就寫到這裏,往後再作補充。
到這篇,JS三座大山系列就暫時完結了,在這其中本身也學到了不少,但願能繼續輸出一些有意義的東西,加油,西瓜君~~

參考文章:

https://www.jianshu.com/p/12b9f73c5a4f
http://www.javashuo.com/article/p-fliavhij-eb.html

若有錯誤,請斧正

以上

 

出處:http://www.javashuo.com/article/p-dztrnnnq-bx.html

相關文章
相關標籤/搜索