成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

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

thursday / 3342人閱讀

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

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

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

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

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

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

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

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

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

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

首先你需要理解如下幾個(gè)概念:

JavaScript中的堆和棧

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

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

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

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

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

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

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

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

單線程(同步和異步)

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

舉一個(gè)簡單的例子來說:

console.log("script start");

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

console.log("script end");

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

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

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

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

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


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

執(zhí)行上下文

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

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

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

對應(yīng)的變量對象是:

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

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

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

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

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

它的第一項(xiàng)永遠(yuǎn)是當(dāng)前作用域(當(dāng)前上下文的變量對象或活動(dòng)對象);

最后一項(xiàng)永遠(yuǎn)是全局作用域(全局執(zhí)行上下文的活動(dòng)對象);

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

this指向當(dāng)前作用域。這里不做過多分析。

執(zhí)行的三個(gè)階段

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

語法分析階段:

閱讀需要支付1元查看
<