」js是一門單線程的語言,js最大的特性是異步「,這些說法已經見慣不怪了,然而不瞭解js的解釋執行始末,這些概念也就只是聽聽,真正遇到問題的時候,也只能一臉懵逼而已~javascript
因爲 Js 是一門單線程的語言,爲了實現異步特性,必須有一種行之有效的機制,Event Loop 就是這種機制java
說 Js 是一門單線程語言,指的是它只有一個用戶執行線程,同一時刻只能執行一個任務,在你看不見的地方,還須要有不少其它線程/進程來調度ajax
當程序開始運行的時候,將默認執行全局執行上下文(將在後文中說明)中的代碼,若是遇到同步函數,那麼將把當前執行上下文壓入棧中,進入同步函數的執行上下文執行(假若在函數中又遇到了其它同步函數,將持續此過程),當函數執行完成,執行線程將從消息線程中pop一個執行上下文進行執行網絡
global() {
/** * 定義 */
EC3() {
}
EC2() {
EC3(); //調用EC3
}
EC1() {
EC2();//調用EC2
}
//調用EC1
EC1();
}複製代碼
若是當前執行上下文遇到了異步操做,那麼它向事件監聽線程或者計時線程發出通知以後,將繼續執行,此時有兩種狀況:
a) 計時函數,如:閉包
setTimeout(func, 1000);複製代碼
此時,異步通知將發給計時線程(Timer),一秒後,計時線程將會把 func
函數的執行上下文壓入消息線程堆棧,執行線程在處理完當前執行上下文的時候從消息線程堆棧中 Pop 出 func 的執行上下文進行執行(若是在此期間沒有別的執行上下文入棧的話)異步
因此,
setTime(func, n);
並不能保證,在 n 毫秒以後 func 能被執行,這還得看執行線程當前在幹什麼了函數
b) 事件監聽線程,若是發起的是一個異步io操做,如發起一個網絡請求:oop
$.ajax(url, params, callback);複製代碼
那麼異步事件將發給事件監聽線程去監聽,一旦網絡請求完成,一樣的,事件監聽線程將把 callback 的執行上下文入棧,等待執行線程的召喚ui
上文反覆提到執行上下文,下面就對執行上下文進行一個深刻剖析this
所謂執行上下文,就是 Js 執行的時候的一個運行環境/做用域(scope),有以下幾種狀況:
/** * 全局執行上下文/做用域 */
console.log('在全局環境中執行')
function hello() { //hello函數執行上下文/做用域
var say = 'hello';
function world() { //world函數執行上下文
...
}
}複製代碼
全局做用域中的方法、變量,能夠被其它任何函數做用域所訪問,函數做用域中的方法變量,在子函數做用域中能夠訪問,外部沒法直接訪問
經過函數返回的子函數去訪問函數做用域的私有變量,也就造成了閉包
也就是上文提到的 Event Loop,該線程以棧的形式保存執行上下文,函數執行上下文的入棧出棧的過程,使得js得以在單線程的狀況下,實現異步特性
每一次函數被調用,js解釋器都會爲之建立新的上下文,此時能夠分爲兩個階段:
a) 上下文的建立階段:函數被調用,但還沒有開始執行(代碼分析預處理階段),此時會爲執行上下文建立做用域鏈,建立變量、函數和參數以及求this的值
executionContextObj = {
scopeChain: { /* 變量對象(variableObject)+ 全部父執行上下文的變量對象*/ },
variableObject: { /*函數 arguments/參數,內部變量和函數聲明 */ },
this: {}
}複製代碼
特別的,變量提高就是在這個階段發生的
b) 執行階段:指派變量的值和函數的引用並解釋執行代碼
下面再用僞代碼的形式來描述一下這個過程:
//函數被調用
1. 建立執行上下文
a) 建立做用域鏈
b) 建立變量、函數和參數
c) 求this值
2. 開始執行在執行上下文上 執行
...
a) 遇到同步函數
b) 當前執行上下文入棧
c) 重複以上過程
...
3. 執行完成,往上一層 執行上下文 返回數據
4. 從執行上下文棧pop出一個新的執行上下文執行複製代碼
瞭解 Js 解釋器一些底層原理,是很是有必要的,當程序的運行結果跟你的預期結果不同的時候,甚至看起來很詭異的時候,如何去解釋,就很體現能力了
更多精彩,邀您關注~~