JavaScript 同步和異步(執行機制)

首先我放在文章的開頭,重點說兩點:javascript

  1. JavaScript 是一門單線程語言。
  2. Event Loop(事件循環)是JavaScript 的執行機制。

好了,那麼咱們開始這篇文章的主要內容,既然說js 是單線程,那就是在執行代碼的時候是從上往下執行的,咱們來看一段代碼吧:java

setTimeout(function(){
    console.log('定時器開始')
});
new Promise(function(resolve){
    console.log('Promise開始');
    resolve();
}).then(function(){
    console.log('執行then函數')
});
console.log('代碼執行結束');
複製代碼

看到上面的代碼,可能你們給出的答案是:'定時器開始','Promise開始','執行then函數','代碼執行結束'。那麼咱們驗證一下,輸出結果倒是:ios

  • Promise開始
  • 代碼執行結束
  • 執行then函數
  • 定時器開始

JavaScript單線程

看到這樣的答案,咱們仍是研究一下他的執行機制吧,至於預解析我後面會單獨發一篇文章單獨說,這裏我們就主要看後面的執行機制了,咱們說JavaScript是一個單線程語言,那他的 "多線程" 是咋來的,都是模擬出來的,一會咱們來看看這個"多線程的紙老虎"。ajax

JavaScript 同步異步(初探事件循環)

既然是單線程,那麼咱們能夠想到,好比去銀行辦理業務,到了那裏要去排隊一個一個辦理,一樣的JavaScript的任務也是一個一個執行,若是前面一個用時很長,那後面哪個就得等着,等到前面的執行完再繼續。如今咱們在打開某一個網站的時候,一般會看到有些圖片加了背景圖,由於頁面中有些圖片,視頻,大批量的數據很是耗時,若是網速很差的電腦去打開,那想而知,空白啦~因此有了如今的 同步 和 異步!~axios

  • 同步和異步任務分別進入不一樣的執行環境,同步的進入主線程,異步的寫入Event Table事件列表中。
  • 當事件完成時,把事件列表中的任務推入Event queue 事件隊列,等待執行。
  • 主線程完成全部任務,就去查看事件隊列裏面,有的話拿出來執行。
  • 上面這個步驟會重複執行,知道沒有可執行的任務,造成事件循環(Event Loop);
axios.post('/user', { name: 'fly', age: '30' })
.then(function (response) {
    console.log(response);
 })
console.log('代碼執行結束');
複製代碼
  • axios 進入事件列表。開始執行主線程,遇到console.log("代碼執行結束");
  • 而後axios執行完畢,回到函數進入事件隊列 Event Queue;
  • 主線程沒有任務了去事件隊列裏面去拿,拿到了回調函數執行;

setTimeout

相信setTimeout你們並不陌生,在工做中可能也會用到,異步函數,還能延遲執行~promise

setTimeout(() => {
    console.log('執行setTimeout');
},3000)
console.log('執行console');
複製代碼

按照咱們前面的思路,先執行console.log('執行console');而後執行console.log('執行setTimeout');答案沒錯,那再變換一下:瀏覽器

setTimeout(() => {
    abc();
},3000)
exercise(100000000);//假設有個運動函數,須要時間很長...並且不固定...大於3秒,可能10秒
複製代碼

那麼咱們在運行結果,發現3秒後並無執行console.log('執行setTimeout');bash

  • setTimeout 異步函數推到異步任務中,寫入事件列表。
  • 主程序執行exercise函數,1秒,2秒,3秒.......等待中。
  • 三秒時間到,abc() 推入事件隊列Event Queue,等待執行,可是exercise函數還沒執行完,那就等着吧~
  • 終於執行完了,這是再去事件隊列去找要執行的任務,abc()進入主線程,執行。

上面的流程走完,咱們知道setTimeout這個函數,是通過指定時間後,把要執行的任務(本例中爲abc())加入到Event Queue中,又由於是單線程任務要一個一個執行,若是前面的任務須要的時間過久,那麼只能等着,致使真正的延遲時間遠遠大於3秒。有時候咱們會看到這樣的形式setTimeout(function(),0); 0秒是否是當即就執行了呢?其實並非多線程

那他的意識是說,不須要多少秒進入Event Queue ,待主線程任務走完,就回去執行。 仍是剛纔的例子:異步

setTimeout(() => {
    abc();
},0)
console.log('執行console');
//  1.執行console   2.abc()
複製代碼

這裏說一下,其實看到有筆者說,setTimeout有最小時間間隔限制,HTML5標準爲4ms,小於4ms按照4ms處理,可是每一個瀏覽器實現的最小間隔都不一樣。 setInterval的最短間隔時間是10毫秒,也就是說,小於10毫秒的時間間隔會被調整到10毫秒。這裏先記下吧。

setInterval

兩個定時器兄弟,原理同樣,只不過setInterval會每隔指定的時間將註冊的函數置入Event Queue。若是前面的任務時間過長,也會等待,不過這裏注意一點,若是其餘的函數執行時間過長,定時器會執行完畢會把這些任務從時間列表拿到事件隊列,若是時間唱的函數執行後,超出了延時執行的時間,那麼就會挨個的拿到主程序執行,看不出延時時間了。

Promise

這裏簡單說一下Promise的做用,細節就很少說了(還須要總結好多...),Promise對象是用於異步操做的。在實際的開發中,咱們是否是會遇到這種狀況:

$.ajax({
    url:www.javascript.com,
    data:data,
    success:() => {
        console.log('第一個數據返回成功!');
        $.ajax({
            url:www.javascript.com,
            data:data,
            success:() => {
                console.log('第二個數據返回成功!');
                ...若是還有再繼續
            }
        })
    }
})
複製代碼

看到這段代碼,應該不陌生吧,相信你們必定遇到過,"看着還不錯,挺好的~~~"!我信你個gui,糟老頭子壞得很~,那如今Promise派上用場了:

function abc(url,param){
    return new Promise(function (resolve, reject) {
        request(url, param, function(){
            resolve('數據請求成功了');
        }, reject);
    });
}
abc.then(function(data){
    console.log(date);//'第一個數據請求成功了';
    return new Promise(function (resolve, reject) {
            request(url, param, function(){
                resolve('數據請求成功了');
            }, reject)
           });
}).then(function(){
    console.log(date);//'第二個數據請求成功了';
});
複製代碼

這裏不過多說Promise 的用法了,繼續咱們的正題,除了廣義的同步任務和異步任務,咱們對任務有更精細的定義:

  • 宏任務 包含整個script代碼塊,setTimeout,setInterval
  • 微任務 Promise,process.nextTick(這裏先不作敘述)

不一樣類型的任務會進入對應的事件隊列Event Queue,好比setTimeout和setInterval會進入相同的Event Queue。 Promise和process.nextTick會進入相同的。

setTimeout(function() {
    console.log('setTimeout');
})
new Promise(function(resolve) {
    console.log('promise');
    resolve();
}).then(function() {
    console.log('then');
})
console.log('console');
複製代碼

執行結果爲:promise,console,then,setTimeout

  • 這段代碼做爲宏任務,進入主線程。
  • 先看到setTimeout,那麼註冊後把他放到宏任務事件隊列Event Queue。
  • 接下來到了Promise,new Promise當即執行,then函數分發到微任務Event Queue。
  • 遇到console.log(),當即執行。
  • 到如今第一次的宏任務執行結束。那麼得看看有沒有微任務啊,有,then 裏面有啊,拿出來執行。
  • 第一輪事件循環結束了,咱們開始第二輪循環,固然要從宏任務Event Queue開始。咱們發現了宏任務Event Queue中setTimeout對應的回調函數,當即執行。
  • 執行結束。

事件循環,宏任務,微任務的關係如圖(這裏看到不少做者用到的一張圖,我也那過來吧,謝謝圖片做者):

那下面就拿到一段代碼了,在網上也能看到:

console.log('1');

setTimeout(function() {
    console.log('2');
    process.nextTick(function() {
        console.log('3');
    })
    new Promise(function(resolve) {
        console.log('4');
        resolve();
    }).then(function() {
        console.log('5')
    })
})
process.nextTick(function() {
    console.log('6');
})
new Promise(function(resolve) {
    console.log('7');
    resolve();
}).then(function() {
    console.log('8')
})

setTimeout(function() {
    console.log('9');
    process.nextTick(function() {
        console.log('10');
    })
    new Promise(function(resolve) {
        console.log('11');
        resolve();
    }).then(function() {
        console.log('12')
    })
})
附上輸出結果:1,7,6,8,2,4,3,5,9,11,10,12
複製代碼

在此要感謝網上看到的各位做者的文章(重點看到的csdn,子曉),以爲寫的很不錯,這裏也仿照着來記錄一下,也加了一下本身見解,但願能給更多的人帶來幫助~~~

相關文章
相關標籤/搜索