成人无码视频,亚洲精品久久久久av无码,午夜精品久久久久久毛片,亚洲 中文字幕 日韩 无码

資訊專欄INFORMATION COLUMN

從底層看JS執(zhí)行機制

thursday / 3402人閱讀

摘要:作用域鏈用于表明上下文的執(zhí)行順序。當前上下文執(zhí)行完畢則出棧,執(zhí)行下一個上下文。

從一個簡單的例子出發(fā)

先從一個簡單的例子出發(fā)(先不涉及異步),看看自己是否大致了解瀏覽器的執(zhí)行機制:

console.log(a);
var a=1;
function foo(a){
    console.log(a);
    var a=2;
    console.log(a);
}
foo(a);
執(zhí)行結(jié)果:

undefined 1 2

如果你的預測結(jié)果不一樣,那你可以看看下面幾個常見的誤區(qū):

var a=1不是進行了變量提升?為什么第一個打印的結(jié)果為 undefined?

答:變量提升只提升變量的聲明,并不進行賦值。其中變量提升發(fā)生在預編譯階段,此時a=undefined,預編譯結(jié)束后代碼如下
//函數(shù)聲明和變量聲明進行提升,且函數(shù)聲明優(yōu)先級更高
function foo(a){
    console.log(a);
    var a=2;
    console.log(a);
}
var a;
console.log(a);
a=1;
foo(a);

很明顯第一個結(jié)果為undefined。

foo函數(shù)中參數(shù)名和變量名都為a,使用時該以哪一個a為準?

變量聲明在順序上跟在函數(shù)聲明和形式參數(shù)聲明之后,同時,如果變量名稱跟已經(jīng)聲明的形式參數(shù)或函數(shù)相同,則變量聲明不會干擾已經(jīng)存在的這類屬性。例子中的var a=2,可以拆分為var a;a=2;其中a=2是對參數(shù)a進行賦值。
現(xiàn)在我們詳細地說一說js的執(zhí)行機制:

首先你需要理解如下幾個概念:

JavaScript中的堆和棧

棧(stack) 棧stack為自動分配的內(nèi)存空間,它由系統(tǒng)自動釋放;

堆(heap) 堆heap是動態(tài)分配的內(nèi)存,大小不定也不會自動釋放。

JavaScript 中的變量分為基本類型和引用類型。其中,基本類型存在于棧中,引用類型存在于堆中。在js的執(zhí)行階段,當執(zhí)行到a=2這樣的賦值語句時,js引擎線程會先判斷2是基本類型還是引用類型,如果它是基本類型,則直接對執(zhí)行棧中的AO進行賦值a=2(AO會在下面的執(zhí)行上下文中講到),若是引用類型,則在堆中存入2,然后用2在堆中的地址對AO進行賦值。
執(zhí)行環(huán)境

js的執(zhí)行環(huán)境分為三種:

全局環(huán)境(JS代碼加載完畢后,進入代碼預編譯即進入全局環(huán)境)

函數(shù)環(huán)境(函數(shù)調(diào)用執(zhí)行時,進入該函數(shù)環(huán)境,不同的函數(shù)則函數(shù)環(huán)境不同)

eval(不建議使用,會有安全,性能等問題)

js每進入一個執(zhí)行環(huán)境就會創(chuàng)建一個執(zhí)行上下文,并將它放入執(zhí)行棧中。執(zhí)行上下文會在下文講到。

單線程(同步和異步)

js是一門單線程語言,但并不意味著參與js執(zhí)行過程的線程就只有一個。一個有四個線程參與該過程:
JS引擎線程、事件觸發(fā)線程、定時器觸發(fā)線程、HTTP異步請求線程。其中,只有JS引擎線程在執(zhí)行JS腳本程序,其他三個線程只負責將滿足觸發(fā)條件的處理函數(shù)推進事件隊列,等待JS引擎線程執(zhí)行。

舉一個簡單的例子來說:

console.log("script start");

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

console.log("script end");

JS引擎主線程按代碼順序執(zhí)行,當執(zhí)行到console.log("script start");,JS引擎主線程認為該任務是同步任務,所以立刻執(zhí)行輸出script start,然后繼續(xù)向下執(zhí)行;

JS引擎主線程執(zhí)行到setTimeout(function() { console.log("setTimeout"); }, 0);,JS引擎主線程認為setTimeout是異步任務API,則向瀏覽器內(nèi)核進程申請開啟定時器線程進行計時和控制該setTimeout任務。由于W3C在HTML標準中規(guī)定setTimeout低于4ms的時間間隔算為4ms,那么當計時到4ms時,定時器線程就把該回調(diào)處理函數(shù)推進任務隊列中等待主線程執(zhí)行,然后JS引擎主線程繼續(xù)向下執(zhí)行;

JS引擎主線程執(zhí)行到console.log("script end");,JS引擎主線程認為該任務是同步任務,所以立刻執(zhí)行輸出script end;

JS引擎主線程上的任務執(zhí)行完畢(輸出script start和script end)后,主線程空閑,則開始讀取任務隊列中的事件任務,將該任務隊里的事件任務推進主線程中,按任務隊列順序執(zhí)行,最終輸出setTimeout,所以輸出的結(jié)果順序為script start script end setTimeout;

如果還不清楚,可以看看下圖:


首先,這是一個瀏覽器環(huán)境,其中主線程操作堆和執(zhí)行棧,而RunTime中存在著許多web API,當主線程讀取到setTimeOut等API時,它會交給其他線程來處理(setTimeOut則是定時器觸發(fā)線程),定時器觸發(fā)線程會先將setTimeOut中的回調(diào)函數(shù)存放在event table中,當滿足觸發(fā)條件時(如上面的4ms),就將回調(diào)函數(shù)推入事件隊列(callback queue)中,等待主線程空閑(執(zhí)行棧中為空),回調(diào)函數(shù)則被推入執(zhí)行棧中進行執(zhí)行。

執(zhí)行上下文

執(zhí)行上下文可理解為當前的執(zhí)行環(huán)境,與該運行環(huán)境相對應。js引擎每進入一個環(huán)境就會創(chuàng)建相應的執(zhí)行上下文,創(chuàng)建執(zhí)行上下文的過程中,主要做了以下三件事件,如圖:

其中,變量對象VO(Variable object)用于存放聲明后的變量、函數(shù)和形參。我們舉一個例子來說:

var a = 10;
 
function test(x) {
  var b = 20;
};
 
test(30);

對應的變量對象是:

// 全局上下文的變量對象
VO(global) = {
  a: undefined,
  test: 
  //是test函數(shù)位于堆中的地址
};
 
// test函數(shù)上下文的變量對象
VO(test) = {
  arguments: {
      x:undefined,
      length:1
  },
  b: undefined
};

當預編譯結(jié)束,js進入解釋執(zhí)行階段時,VO就會轉(zhuǎn)化為AO(Active object),也就是活動對象。AO中變量和參數(shù)的值不再是undefined,它們的值會隨著js的逐步執(zhí)行而發(fā)生變化。

作用域鏈用于表明上下文的執(zhí)行順序。上例中的作用域鏈為:

 scopeChain: [VO(test),  AO(global)],

我們這里直接使用數(shù)組表示作用域鏈,作用域鏈的活動對象或變量對象可以直接理解為作用域;

它的第一項永遠是當前作用域(當前上下文的變量對象或活動對象);

最后一項永遠是全局作用域(全局執(zhí)行上下文的活動對象);

作用域鏈保證了變量和函數(shù)的有序訪問,查找方式是沿著作用域鏈從左至右查找變量或函數(shù),找到則會停止查找,找不到則一直查找到全局作用域,再找不到則會拋出引用錯誤。

this指向當前作用域。這里不做過多分析。

執(zhí)行的三個階段

js的執(zhí)行分為三個階段:

語法分析階段:

閱讀需要支付1元查看
<