ES6 事件循環機制

執行上下文(Execution Context)

JavaScript中的運行環境大概包括三種狀況:html

  • 全局環境:JavaScript代碼運行起來會首先進入該環境
  • 函數環境:當函數被調用執行時,會進入當前函數中執行代碼
  • eval:存在安全問題(由於它能夠執行傳給它的任何字符串,因此永遠不要傳入字符串或者來歷不明和不受信任源的參數)不建議使用,可忽略

每次當控制器轉到可執行代碼的時候,就會進入一個執行上下文。執行上下文能夠理解爲當前代碼的執行環境,它會造成一個做用域。前端

函數調用棧(call stack)

所以在一個JavaScript程序中,一定會產生多個執行上下文,JavaScript引擎會以函數調用棧的方式來處理它們。棧底永遠都是全局上下文,而棧頂就是當前正在執行的上下文。html5

var color = 'blue';

function changeColor() {
    var anotherColor = 'red';

    function swapColors() {
        var tempColor = anotherColor;
        anotherColor = color;
        color = tempColor;
    }

    swapColors();
}
changeColor();
複製代碼

注意:函數中,遇到 return 能直接終止可執行代碼的執行,所以會直接將當前上下文彈出棧。git

全局上下文的生命週期,與程序的生命週期一致,只要程序運行不結束,好比關掉瀏覽器窗口,全局上下文就會一直存在。其餘全部的上下文環境,都能直接訪問全局上下文的屬性。github

解了這個過程以後,咱們就能夠對執行上下文作一些總結:面試

  • 單線程
  • 同步執行,只有棧頂的上下文處於執行中,其餘上下文須要等待
  • 全局上下文只有惟一的一個,它在瀏覽器關閉時出棧
  • 函數的執行上下文的個數沒有限制
  • 每次某個函數被調用,就會有個新的執行上下文爲其建立,即便是調用的自身函數
執行上下文生命週期

一個執行上下文的生命週期能夠分爲兩個階段:數組

  • 建立階段:在這個階段中,執行上下文會分別建立變量對象,創建做用域鏈,以及肯定this的指向。promise

    1. 建立變量對象:
      1. 創建arguments對象:檢查當前上下文中的參數,創建該對象下的屬性與屬性值。
      2. 檢查當前上下文的函數聲明:在變量對象中以函數名創建一個屬性,屬性值爲指向該函數所在內存地址的引用,若是函數名的屬性已經存在,那麼該屬性將會被新的引用所覆蓋。
      3. 檢查當前上下文中的變量聲明:每找到一個變量聲明,在變量對象中以變量名創建一個屬性,屬性值爲undefined,若是該變量名的屬性已經存在,爲了防止同名的函數被修改成undefined,則會直接跳過,原屬性值不會被修改。
    2. 創建做用域鏈:做用域鏈,是由當前環境與上層環境的一系列變量對象組成,它保證了當前執行環境對符合訪問權限的變量和函數的有序訪問
    3. 肯定this的指向:this的指向,是在函數被調用的時候肯定的,在函數執行過程當中,this一旦被肯定,就不可更改了。若是調用者函數,被某一個對象所擁有,那麼該函數在調用時,內部的this指向該對象。若是函數獨立調用,那麼該函數內部的this,則指向undefined。
  • 代碼執行階段:建立完成以後,就會開始執行代碼,這個時候,會完成變量賦值,函數引用,以及執行其餘代碼。瀏覽器

建立變量對象:安全

例子1:

function test() {
    console.log(a);
    console.log(foo());

    var a = 1;
    function foo() {
        return 2;
    }
}
test();
複製代碼

等價於

function test() {
    function foo() {
        return 2;
    }
    var a;
    console.log(a);
    console.log(foo());
    a = 1;
}

test();
複製代碼

例子2:

function test() {
    console.log(foo);
    console.log(bar);

    var foo = 'Hello';
    console.log(foo);
    var bar = function () {
        return 'world';
    }

    function foo() {
        return 'hello';
    }
}

test();
複製代碼

等價於

function test() {
    function foo() {
        return 'hello';
    }
    var bar;

    console.log(foo);
    console.log(bar);
    foo = 'Hello';
    console.log(foo);
    var bar = function () {
        return 'world';
    }
}

test();
複製代碼

未進入執行階段以前,變量對象中的屬性都不能訪問。可是進入執行階段以後,變量對象轉變爲了活動對象,裏面的屬性都能被訪問了,而後開始進行執行階段的操做。 變量對象和活動對象其實都是同一個對象,只是處於執行上下文的不一樣生命週期。不過只有處於函數調用棧棧頂的執行上下文中的變量對象,纔會變成活動對象。 咱們能夠用建立變量對象來理解變量提高。

創建做用域鏈: 做用域鏈,是由當前環境與上層環境的一系列變量對象組成,它保證了當前執行環境對符合訪問權限的變量和函數的有序訪問。

var a = 20;

function test() {
    var b = a + 10;

    function innerTest() {
        var c = 10;
        return b + c;
    }

    return innerTest();
}

test();
複製代碼

肯定this的指向: this的指向,是在函數被調用的時候肯定的,在函數執行過程當中,this一旦被肯定,就不可更改了。若是調用者函數,被某一個對象所擁有,那麼該函數在調用時,內部的this指向該對象。若是函數獨立調用,那麼該函數內部的this,則指向undefined。

// demo01
var a = 20;
function fn() {
    console.log(this.a);
}
fn();
複製代碼
// demo02
var a = 20;
function fn() {
    function foo() {
        console.log(this.a);
    }
    foo();
}
fn();
複製代碼
// demo03
var a = 20;
var obj = {
    a: 10,
    c: this.a + 20,
    fn: function () {
        return this.a;
    }
}

console.log(obj.c);
console.log(obj.fn());
複製代碼

使用call,apply顯示指定this

function fn() {
    console.log(this.a);
}
var obj = {
    a: 20
}

fn.call(obj);
複製代碼

call與applay後面的參數,都是向將要執行的函數傳遞參數。其中call以一個一個的形式傳遞,apply以數組的形式傳遞。這是他們惟一的不一樣。

function fn(num1, num2) {
    console.log(this.a + num1 + num2);
}
var obj = {
    a: 20
}

fn.call(obj, 100, 10); 
fn.apply(obj, [20, 10]); 
複製代碼
事件循環機制

JS 引擎創建在單線程事件循環的概念上。單線程( Single-threaded )意味着同一時刻只能執行一段代碼,與 Swift、 Java 或 C++ 這種容許同時執行多段不一樣代碼的多線程語言造成了反差。

JavaScript代碼的執行過程當中,除了依靠函數調用棧來搞定函數的執行順序外,還依靠任務隊列(task queue)來搞定另一些代碼的執行。

  • 一個線程中,事件循環是惟一的,可是任務隊列能夠擁有多個。
  • 任務隊列又分爲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代碼的執行順序。它從script(總體代碼)開始第一次循環。以後全局上下文進入函數調用棧。直到調用棧清空(只剩全局),而後執行全部的micro-task。當全部可執行的micro-task執行完畢以後。循環再次從macro-task開始,找到其中一個任務隊列執行完畢,而後再執行全部的micro-task,這樣一直循環下去。
  • 其中每個任務的執行,不管是macro-task仍是micro-task,都是藉助函數調用棧來完成。

例子1:

setTimeout(function() {
    console.log('timeout1');
})

new Promise(function(resolve) {
    console.log('promise1');
    for(var i = 0; i < 1000; i++) {
        i == 99 && resolve();
    }
    console.log('promise2');
}).then(function() {
    console.log('then1');
})

console.log('global1');
複製代碼

例子2:

// demo02
console.log('glob1');

setTimeout(function() {
    console.log('timeout1');
    process.nextTick(function() {
        console.log('timeout1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout1_promise');
        resolve();
    }).then(function() {
        console.log('timeout1_then')
    })
})

setImmediate(function() {
    console.log('immediate1');
    process.nextTick(function() {
        console.log('immediate1_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate1_promise');
        resolve();
    }).then(function() {
        console.log('immediate1_then')
    })
})

process.nextTick(function() {
    console.log('glob1_nextTick');
})
new Promise(function(resolve) {
    console.log('glob1_promise');
    resolve();
}).then(function() {
    console.log('glob1_then')
})

setTimeout(function() {
    console.log('timeout2');
    process.nextTick(function() {
        console.log('timeout2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('timeout2_promise');
        resolve();
    }).then(function() {
        console.log('timeout2_then')
    })
})

process.nextTick(function() {
    console.log('glob2_nextTick');
})
new Promise(function(resolve) {
    console.log('glob2_promise');
    resolve();
}).then(function() {
    console.log('glob2_then')
})

setImmediate(function() {
    console.log('immediate2');
    process.nextTick(function() {
        console.log('immediate2_nextTick');
    })
    new Promise(function(resolve) {
        console.log('immediate2_promise');
        resolve();
    }).then(function() {
        console.log('immediate2_then')
    })
})
複製代碼

80% 應聘者都不及格的 JS 面試題

Excuse me?這個前端面試在搞事!

yangbo5207.github.io/wutongluo/

相關文章
相關標籤/搜索