今天和一個正在學習前端的朋友聊天 , 學習到了node.js這個內容 。
首先呢 , node.js 是一個基於 Chrome V8 引擎的 JavaScript 運行環境。 node.js 使用了一個事件驅動、非阻塞式 I/O 的模型,使其輕量又高效。
等等之類的 ...
最后他問了我一道題目
console.log('111');
setTimeout(()=>{
console.log('222')
},1000);
console.log('333');
setTimeout(()=>{
console.log('444')
},0);
console.log(555);
就是執行這樣一段js代碼 , 打印出來的結果是 :
111 -> 333 -> 555 -> 444 -> 222
(結果就是我們理論起來 setTimeout 到底是同步還是異步的 ?為什么會這樣執行 ? JavaScript不是一門單線程的語言嗎?)
下面來給大家分析一下(如果錯誤 ,歡迎糾正)
一、為什么JavaScript是單線程?
javascript語言的最大的特點之一就是單線程 ,意思就是 :只能同一時間執行同一段代碼。就比如說: 我這一句循環代碼需要5s來執行 ,那么后面所有的代碼都要等着我這個循環結束才能繼續執行 。
那么,為什么JavaScript不能像Java一樣有多個線程呢?
這是因為跟這門語言的用途有關。JavaScript作為瀏覽器腳本語言,主要用途是與用戶互動,以及操作DOM。這就決定了它只能是單線程 ,否則會帶來很多復雜的同步問題。為了避免復雜性,這個特性我相信將來也不會改變。
最新的HTML5提出了Web Worker標准,允許JS腳本創建多個線程,但是子線程完全受主線程控制,不能操作DOM。所以JS還是單線程。
二、為什么上述代碼會這樣執行 ?
這就需要了解 任務隊列
我們把需要執行的代碼看成一個個任務,把任務分成兩種,同步任務(sknchronous),異步任務(asynchronous)。
下面是它們的運行機制:
1.所有同步任務都在主線程上,形成一個執行棧
2.主線程之外還有一個“任務隊列”,只要異步任務有了運行結果 ,就在任務隊列中放一個事件
3.當執行棧中所有的任務執行完了,就去看看任務隊列中有沒有需要執行的事件 ,如果有的話,就結束它們的等待,進入執行棧 ,開始執行。
4.主線程不斷重讀上面三步
(這里還是單線程,只是多了一個任務隊列)

三、Event Loop
主線程從"任務隊列"中讀取事件,這個過程是循環不斷的,所以整個的這種運行機制又稱為Event Loop(事件循環)。這是計算機系統的一種運行機制。
JavaScript語言就采用這種機制,來解決單線程運行帶來的一些問題。

上圖中,主線程運行的時候,產生堆和棧,棧中的代碼調用各種外部API,它們在"任務隊列"中加入各種事件。只要棧中的代碼執行完畢,主線程就會去讀取"任務隊列",依次執行那些事件所對應的回調函數。
執行棧中的代碼(同步任務),總是在讀取"任務隊列"(異步任務)之前執行。
四、setTimeout 到底是同步還是異步的 ?
除了放置異步任務的事件,"任務隊列"還可以放置定時事件。
setTimeout()接受兩個參數,第一個是回調函數,第二個是推遲執行的毫秒數。
需要注意的是,setTimeout()只是將事件插入了"任務隊列",必須等到當前代碼(執行棧)執行完,主線程才會去執行它指定的回調函數。要是當前代碼耗時很長,有可能要等很久,所以並沒有辦法保證,回調函數一定會在setTimeout()指定的時間執行。
綜上所屬
setTimeout是單線程,類似異步,但不是異步 。
最后在這里提一句 :
1.異步的三種實現方式
1) 回調函數
回調函數不一定是異步 , 但異步一定有回調函數
2) 事件
3) promise 承諾對象