javascript執行機制

對於javascript執行機制,大家應該並不陌生,很多面試題都會出現,比如:

  setTimeout(function () {
        console.log('定時器')
    });
    new Promise(function (resolve) {
        console.log('Promise');
        resolve();
    }).then(function () {
        console.log('Promise回調')
    });
    console.log('結束');
    //'Promise'
    //'結束'
    //'Promise回調'
    //'定時器'

上面這個面試題,如果不懂javascript執行機制的話估計很難做的出來!

一.javascript是一門單線程語言

js這門語言最大的特點就是單線程,即同一個時間只能做一件事情,做完一件事情纔可以再做別的事情,好比很多人在銀行排隊取錢,你是窗口服務員,你每次只能爲一個客戶取錢,不能一次爲多個客戶取錢,那不亂套了嗎?JavaScript的單線程,與它的用途有關。作爲瀏覽器腳本語言,JavaScript的主要用途是與用戶互動,以及操作DOM。這決定了它只能是單線程,否則會帶來很複雜的同步問題。比如,假定JavaScript同時有兩個線程,一個線程在某個DOM節點上添加內容,另一個線程刪除了這個節點,這時瀏覽器應該以哪個線程爲準?所以,爲了避免複雜性,從一誕生,JavaScript就是單線程,這已經成了這門語言的核心特徵,將來也不會改變。爲了利用多核CPU的計算能力,HTML5提出Web Worker標準,允許JavaScript腳本創建多個線程,但是子線程完全受主線程控制,且不得操作DOM。所以一切javascript版的"多線程"都是用單線程模擬出來的,一切javascript多線程都是紙老虎!這個新標準並沒有改變JavaScript單線程的本質。

二.javascript事件循環機制

單線程就意味着,所有任務需要排隊,前一個任務結束,纔會執行後一個任務。如果前一個任務耗時很長,後一個任務就不得不一直等着。好比之前說的排隊取錢,如果一個客戶取錢要花很多時間,後面的人只能一直等着,但是我們在瀏覽頁面網站的時候,有些圖片要通過數據請求後再加載纔會顯示,那麼我們整個頁面是不是要等某個比較大的圖片沒加載出來而整個網站都沒顯示出來呢,顯然是不行的,所以JavaScript語言的設計者意識到,於是,所有任務可以分成兩種

1.同步任務(synchronous)

同步任務指的是,在主線程上排隊執行的任務,只有前一個任務執行完畢,才能執行後一個任務,所有同步任務都在主線程上執行,形成一個執行棧(execution context stack)。執行棧執行順序是後進先出。

2.異步任務(asynchronous)

異步任務指的是,不進入主線程、而進入"任務隊列"(task queue)的任務,只有"任務隊列"通知主線程,某個異步任務可以執行了,該任務纔會進入主線程執行。

同步和異步任務分別進入不同的執行"場所",同步的進入主線程,異步的進入事件表(Event Table)並註冊函數。
當指定的事情完成時,事件表(Event Table)會將這個函數移入任務隊列(Event Queue)。
主線程內的任務執行完畢爲空,會去(Event Queue)讀取對應的函數,進入主線程執行。
上述過程會不斷重複,也就是常說的Event Loop(事件循環)。

三.宏任務和微任務的區別

macro-task(宏任務):包括整體代碼script,setTimeout,setInterval
micro-task(微任務):Promise,process.nextTick
事件循環的順序,決定js代碼的執行順序。進入整體代碼(宏任務)後,開始第一次循環。接着執行所有的微任務。然後再次從宏任務開始,找到其中一個任務隊列執行完畢,再執行所有的微任務。在執行宏任務或者微任務過程中,裏面的微任務優先級是大於宏任務的,也就是先執行微任務再執行宏任務並且任務隊列的執行順序是先進先出的(哪個宏任務/微任務先進去,先執行哪個宏任務/微任務)。
在這裏插入圖片描述
我們回來最開始的那段代碼:

 setTimeout(function () {
        console.log('定時器')
    });
    new Promise(function (resolve) {
        console.log('Promise');
        resolve();
    }).then(function () {
        console.log('Promise回調')
    });
    console.log('結束');
    //'Promise'
    //'結束'
    //'Promise回調'
    //'定時器'

我們來分析下這段代碼:
1.首先進去一個宏任務(整個js就是個宏任務)
2.然後一開始看到個setTimeout宏任務,進入Event Table並註冊函數,然後Event Table會將這個函數移入任務隊列(Event Queue)
3.接下來碰到個new Promise 執行同步代碼 console.log(‘Promise’); 所以先輸出’Promise’,
4.接下來碰到then回調微任務,進入Event Table並註冊函數,然後Event Table會將這個函數移入任務隊列(Event Queue)
5.碰到 console.log(‘結束’); 輸出’結束’
6.判斷有沒有執行的微任務?有的,有then回調微任務,執行 console.log(‘Promise回調’) 輸出’Promise回調’,
7.最後開始一輪新的事件循壞,先找到setTimeout宏任務,執行console.log(‘定時器’),輸出 ‘定時器’,再找下有沒有微任務?沒有了,再開始開始一輪新的事件循壞,找有沒有宏任務?也沒有了,所以事件循壞結束

四.定時器(如果不是很瞭解的點擊這裏

定時器功能主要由setTimeout()和setInterval()這兩個函數來完成,它們的內部運行機制完全一樣,區別在於前者指定的代碼是一次性執行,後者則爲反覆執行。以下主要討論setTimeout(),setTimeout()接受兩個參數,第一個是回調函數,第二個是推遲執行的毫秒數。

  console.log(1);
    setTimeout(function () {
        console.log(2);
    }, 1000);
    console.log(3);

上面代碼的執行結果是1,3,2,因爲setTimeout()將 console.log(2) 推遲到1000毫秒之後執行。

setTimeout(function(){console.log(1);}, 0);
console.log(2);

上面代碼的執行結果總是2,1,因爲只有在執行完 console.log(2) 以後會去執行"任務隊列"中的回調函數。
setTimeout(fn,0)的含義是,指定某個任務在主線程最早可得的空閒時間執行,也就是說,儘可能早得執行。需要注意的是,setTimeout()只是將事件插入了"任務隊列",必須等到當前代碼(執行棧)執行完,主線程纔會去執行它指定的回調函數。要是當前代碼耗時很長,有可能要等很久,所以並沒有辦法保證回調函數一定會在setTimeout()指定的時間執行。

   for (var i = 0; i < 5; i++) {
        setTimeout(function () {
            console.log(i);
        },i * 1000);
    };

看看這個代碼輸出什麼,並且輸出的時間間隔是多少?

下面我們來分析下,setTimeout有2個參數,第一個是個函數,第二個是延遲時間,i*1000很顯然是上面的i的值,當i=0時候,相當過0秒把函數先Event Table然後再放進任務隊列,然後i=1時,也是過1秒把函數先註冊再放進任務隊列,後面的也是一樣,根據任務隊列先進先出的順序原則,由於主線程中沒有其他任務,所以直接執行任務隊列的函數,相當每隔1秒執行 console.log(i); 這裏i由於異步的肯定是5了,所以每隔1秒輸出一個5。

   for (var i = 0; i < 5; i++) {
        setTimeout((function (i) {
            console.log(i);
        })(i), i * 1000);
    };

改了下代碼,裏面的函數是個自執行函數,i當參數傳了進去,結果又不一樣了,因爲一開始就會自執行,所以後面的延遲時間沒用,輸出結果幾乎同時輸出1,2,3,4。

好了,到這裏javascript執行機制就差不多已經說完了(如果有不對之處,歡迎指正,不勝感激!!!),歡樂的時光總是過得特別快,又到時候和大家講拜拜!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章