先從一個簡單的例子出發(先不涉及異步),看看本身是否大體瞭解瀏覽器的執行機制:html
console.log(a);
var a=1;
function foo(a){
console.log(a);
var a=2;
console.log(a);
}
foo(a);
複製代碼
undefined 1 2git
若是你的預測結果不同,那你能夠看看下面幾個常見的誤區:github
答:變量提高只提高變量的聲明,並不進行賦值。其中變量提高發生在預編譯階段,此時a=undefined,預編譯結束後代碼以下web
//函數聲明和變量聲明進行提高,且函數聲明優先級更高
function foo(a){
console.log(a);
var a=2;
console.log(a);
}
var a;
console.log(a);
a=1;
foo(a);
複製代碼
很明顯第一個結果爲undefined。數組
變量聲明在順序上跟在函數聲明和形式參數聲明以後,同時,若是變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會干擾已經存在的這類屬性。例子中的var a=2,能夠拆分爲var a;a=2;其中a=2是對參數a進行賦值。瀏覽器
首先你須要理解以下幾個概念:安全
JavaScript 中的變量分爲基本類型和引用類型。其中,基本類型存在於棧中,引用類型存在於堆中。在js的執行階段,當執行到a=2這樣的賦值語句時,js引擎線程會先判斷2是基本類型仍是引用類型,若是它是基本類型,則直接對執行棧中的AO進行賦值a=2(AO會在下面的執行上下文中講到),如果引用類型,則在堆中存入2,而後用2在堆中的地址對AO進行賦值。bash
js的執行環境分爲三種:異步
js每進入一個執行環境就會建立一個執行上下文,並將它放入執行棧中。執行上下文會在下文講到。函數
js是一門單線程語言,但並不意味着參與js執行過程的線程就只有一個。一個有四個線程參與該過程: JS引擎線程、事件觸發線程、定時器觸發線程、HTTP異步請求線程。其中,只有JS引擎線程在執行JS腳本程序,其餘三個線程只負責將知足觸發條件的處理函數推動事件隊列,等待JS引擎線程執行。
舉一個簡單的例子來講:
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0);
console.log('script end');
複製代碼
JS引擎主線程按代碼順序執行,當執行到console.log('script start');,JS引擎主線程認爲該任務是同步任務,因此馬上執行輸出script start,而後繼續向下執行;
JS引擎主線程執行到setTimeout(function() { console.log('setTimeout'); }, 0);,JS引擎主線程認爲setTimeout是異步任務API,則向瀏覽器內核進程申請開啓定時器線程進行計時和控制該setTimeout任務。因爲W3C在HTML標準中規定setTimeout低於4ms的時間間隔算爲4ms,那麼當計時到4ms時,定時器線程就把該回調處理函數推動任務隊列中等待主線程執行,而後JS引擎主線程繼續向下執行;
JS引擎主線程執行到console.log('script end');,JS引擎主線程認爲該任務是同步任務,因此馬上執行輸出script end;
JS引擎主線程上的任務執行完畢(輸出script start和script end)後,主線程空閒,則開始讀取任務隊列中的事件任務,將該任務隊裏的事件任務推動主線程中,按任務隊列順序執行,最終輸出setTimeout,因此輸出的結果順序爲script start script end setTimeout;
若是還不清楚,能夠看看下圖:
首先,這是一個瀏覽器環境,其中主線程操做堆和執行棧,而RunTime中存在着許多web API,當主線程讀取到setTimeOut等API時,它會交給其餘線程來處理(setTimeOut則是定時器觸發線程),定時器觸發線程會先將setTimeOut中的回調函數存放在event table中,當知足觸發條件時(如上面的4ms),就將回調函數推入事件隊列(callback queue)中,等待主線程空閒(執行棧中爲空),回調函數則被推入執行棧中進行執行。
執行上下文可理解爲當前的執行環境,與該運行環境相對應。js引擎每進入一個環境就會建立相應的執行上下文,建立執行上下文的過程當中,主要作了如下三件事件,如圖:
其中,變量對象VO(Variable object)用於存放聲明後的變量、函數和形參。咱們舉一個例子來講:
var a = 10;
function test(x) {
var b = 20;
};
test(30);
複製代碼
對應的變量對象是:
// 全局上下文的變量對象
VO(global) = {
a: undefined,
test: <reference to function>
//<reference to function>是test函數位於堆中的地址
};
// test函數上下文的變量對象
VO(test) = {
arguments: {
x:undefined,
length:1
},
b: undefined
};
複製代碼
當預編譯結束,js進入解釋執行階段時,VO就會轉化爲AO(Active object),也就是活動對象。AO中變量和參數的值再也不是undefined,它們的值會隨着js的逐步執行而發生變化。
做用域鏈用於代表上下文的執行順序。上例中的做用域鏈爲:
scopeChain: [VO(test), AO(global)],
複製代碼
this指向當前做用域。這裏不作過多分析。
js的執行分爲三個階段:
語法分析階段: script標籤加載即開始語法分析,分析整個標籤內的語法錯誤。無錯誤即進入預編譯階段。
預編譯階段: 每進入一個新環境,就進行一次預編譯,同時建立一個執行上下文(包含VO對象、做用域鏈、this,忘了是什麼就往回看看)並放入執行棧中。此時,當前環境會進行必定的函數提高和變量提高,注意:函數提高優先於變量提高。環境中沒有新的函數聲明則進入解釋執行階段。
解釋執行階段: 將當前執行上下文中的VO->AO, 此後,js引擎在當前環境從上到下、從左到右執行代碼,不斷改變AO中的變量等內容。當前上下文執行完畢則出棧,執行下一個上下文。
一樣,咱們舉一個例子進行分析:
var a = 1;
function bar() {
var b = 2;
function foo() {
var c = 3;
}
foo();
console.log(b)
}
function coo() {
alert("hello")
}
bar()
coo()
複製代碼
咱們解釋一下以上過程:
至此,你已基本理解了js的執行機制。