欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

簡(jiǎn)單了解JavaScript中的執(zhí)行上下文和堆棧

 更新時(shí)間:2019年06月24日 09:48:36   作者:前端先鋒  
這篇文章主要介紹了簡(jiǎn)單了解JavaScript中的執(zhí)行上下文和堆棧,你應(yīng)該對(duì)解釋器了解得更清楚:為什么在聲明它們之前可以使用某些函數(shù)或變量?以及它們的值是如何確定的?,需要的朋友可以參考下

什么是執(zhí)行上下文?

JavaScript的執(zhí)行環(huán)境非常重要,當(dāng)JavaScript代碼在行時(shí),會(huì)被預(yù)處理為以下情況之一:

  • Global code - 首次執(zhí)行代碼的默認(rèn)環(huán)境。
  • Function code - 每當(dāng)執(zhí)行流程進(jìn)入函數(shù)體時(shí)。
  • Eval code - 要在eval函數(shù)內(nèi)執(zhí)行的文本。

你可以閱讀大量涉及作用域的在線資料,不過(guò)為了使事情更容易理解,讓我們將術(shù)語(yǔ)“執(zhí)行上下文”視為當(dāng)前代碼的運(yùn)行環(huán)境或作用域。接下來(lái)讓我們看一個(gè)包含global和function / local上下文的代碼示例。

這里沒(méi)有什么特別之處,我們有一個(gè)由紫色邊框表示的全局上下文,和由綠色,藍(lán)色和橙色邊框表示的3個(gè)不同的函數(shù)上下文。 只能有1個(gè)全局上下文,可以從程序中的任何其他上下文訪問(wèn)。

你可以擁有任意數(shù)量的函數(shù)上下文,并且每個(gè)函數(shù)調(diào)用都會(huì)創(chuàng)建一個(gè)新的上下文,從而創(chuàng)建一個(gè)私有作用域,其中無(wú)法從當(dāng)前函數(shù)作用域外直接訪問(wèn)函數(shù)內(nèi)部聲明的任何內(nèi)容。 在上面的示例中,函數(shù)可以訪問(wèn)在其當(dāng)前上下文之外聲明的變量,但外部上下文無(wú)法訪問(wèn)在其中聲明的變量或函數(shù)。 為什么會(huì)這樣呢? 這段代碼究竟是如何處理的?

Execution Context Stack(執(zhí)行上下文堆棧)

瀏覽器中的JavaScript解釋器被實(shí)現(xiàn)為單個(gè)線程。 實(shí)際上這意味著在瀏覽器中一次只能做一件事,其他動(dòng)作或事件在所謂的執(zhí)行堆棧中排隊(duì)。 下圖是單線程堆棧的抽象視圖:

我們已經(jīng)知道,當(dāng)瀏覽器首次加載腳本時(shí),它默認(rèn)進(jìn)入全局上下文執(zhí)行。 如果在全局代碼中調(diào)用函數(shù),程序的順序流進(jìn)入被調(diào)用的函數(shù),創(chuàng)建新的執(zhí)行上下文并將其推送到執(zhí)行堆棧的頂部。

如果在當(dāng)前函數(shù)中調(diào)用另一個(gè)函數(shù),則會(huì)發(fā)生同樣的事情。 代碼的執(zhí)行流程進(jìn)入內(nèi)部函數(shù),該函數(shù)創(chuàng)建一個(gè)新的執(zhí)行上下文,該上下文被推送到現(xiàn)有堆棧的頂部。 瀏覽器將始終執(zhí)行位于堆棧頂部的當(dāng)前執(zhí)行上下文,并且一旦函數(shù)執(zhí)行完當(dāng)前執(zhí)行上下文后,它將從棧頂部彈出,把控制權(quán)返回到當(dāng)前棧中的下一個(gè)上下文。 下面的示例顯示了遞歸函數(shù)和程序的執(zhí)行堆棧:

(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));

代碼簡(jiǎn)單地調(diào)用自身3次,并將i的值遞增1。每次調(diào)用函數(shù)foo時(shí),都會(huì)創(chuàng)建一個(gè)新的執(zhí)行上下文。 一旦上下文完成執(zhí)行,它就會(huì)彈出堆棧并且講控制返回到它下面的上下文,直到再次達(dá)到全局上下文。
關(guān)于執(zhí)行堆棧execution stack有5個(gè)關(guān)鍵要點(diǎn):

  • 單線程。
  • 同步執(zhí)行。
  • 一個(gè)全局上下文。
  • 任意多個(gè)函數(shù)上下文。
  • 每個(gè)函數(shù)調(diào)用都會(huì)創(chuàng)建一個(gè)新的執(zhí)行上下文execution context,甚至是對(duì)自身的調(diào)用。

執(zhí)行上下文的細(xì)節(jié)

所以我們現(xiàn)在知道每次調(diào)用一個(gè)函數(shù)時(shí),都會(huì)創(chuàng)建一個(gè)新的執(zhí)行上下文。 但是,在JavaScript解釋器中,對(duì)執(zhí)行上下文的每次調(diào)用都有兩個(gè)階段:

1.創(chuàng)建階段 [調(diào)用函數(shù)時(shí),但在執(zhí)行任何代碼之前]:

  • 創(chuàng)建作用域鏈。
  • 創(chuàng)建變量,函數(shù)和參數(shù)。
  • 確定“this”的值。

2.激活/代碼執(zhí)行階段:

  • 分配值,引用函數(shù)和解釋/執(zhí)行代碼。

可以將每個(gè)執(zhí)行上下文在概念上表示為具有3個(gè)屬性的對(duì)象:

executionContextObj = {
'scopeChain': { /* variableObject + 所有父執(zhí)行上下文的variableObject */ },
'variableObject': { /* 函數(shù)實(shí)參/形參,內(nèi)部變量和函數(shù)聲明 */ },
'this': {}
}

激活對(duì)象/變量對(duì)象 [AO/VO]

在調(diào)用該函數(shù),并且在實(shí)際執(zhí)行函數(shù)之前,會(huì)創(chuàng)建這個(gè)executionContextObj。 這被稱(chēng)為第1階段,即創(chuàng)造階段。 這時(shí)解釋器通過(guò)掃描函數(shù)傳遞的實(shí)參或形參、本地函數(shù)聲明和局部變量聲明來(lái)創(chuàng)建executionContextObj。 此掃描的結(jié)果將成為executionContextObj中的variableObject。

以下是解釋器如何預(yù)處理代碼的偽代碼概述:

1.找一些代碼來(lái)調(diào)用一個(gè)函數(shù)。

2.在執(zhí)行功能代碼之前,創(chuàng)建執(zhí)行上下文。

3.進(jìn)入創(chuàng)建階段:

  • 初始化作用域鏈。
  • 創(chuàng)建variable object:
    • 創(chuàng)建arguments object,檢查參數(shù)的上下文,初始化名稱(chēng)和值并創(chuàng)建引用副本。
    • 掃描上下文以獲取函數(shù)聲明:
      • 對(duì)于找到的每個(gè)函數(shù),在variable object中創(chuàng)建一個(gè)屬性,該屬性是函數(shù)的確切名稱(chēng),該屬性存在指向內(nèi)存中函數(shù)的引用指針。
      • 如果函數(shù)名已存在,則將覆蓋引用指針值。
    • 掃描上下文以獲取變量聲明:
      • 對(duì)于找到的每個(gè)變量聲明,在variable object中創(chuàng)建一個(gè)屬性作為變量名稱(chēng),并將該值初始化為undefined。
      • 如果變量名稱(chēng)已存在于variable object中,則不執(zhí)行任何操作并繼續(xù)掃描。
  • 確定上下文中“this”的值。

4.激活/執(zhí)行階段:

  • 在上下文中運(yùn)行/解釋函數(shù)代碼,并在代碼逐行執(zhí)行時(shí)分配變量值。

我們來(lái)看一個(gè)例子:

function foo(i) {
var a = 'hello';
var b = function privateB() {
};
function c() {
}
}
foo(22);

在調(diào)用foo(22)時(shí),創(chuàng)建階段如下所示:

fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}

如你所見(jiàn),創(chuàng)建階段處理定義屬性的名稱(chēng),而不是為它們賦值,但正式的形參/實(shí)參除外。創(chuàng)建階段完成后,執(zhí)行流程進(jìn)入函數(shù),激活/代碼執(zhí)行階段在函數(shù)執(zhí)行完畢后如下所示:

fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: 'hello',
b: pointer to function privateB()
},
this: { ... }
}

關(guān)于hoisting

你可以找到許多使用JavaScript定義術(shù)語(yǔ)hoisting的在線資源,解釋變量和函數(shù)聲明被hoisting到其函數(shù)范圍的頂部。 但是沒(méi)有人能夠詳細(xì)解釋為什么會(huì)發(fā)生這種情況,掌握了關(guān)于解釋器如何創(chuàng)建激活對(duì)象的新知識(shí),很容易理解為什么。 請(qǐng)看下面的代碼示例:

(function() {
console.log(typeof foo); // function pointer
console.log(typeof bar); // undefined
var foo = 'hello',
bar = function() {
return 'world';
};
function foo() {
return 'hello';
}
}());

我們現(xiàn)在可以回答的問(wèn)題是:

  • 為什么我們可以在聲明foo之前就能訪問(wèn)?
    • 如果我們理解了創(chuàng)建階段,就知道在激活/代碼執(zhí)行階段之前已經(jīng)創(chuàng)建了變量。因此,當(dāng)函數(shù)流開(kāi)始執(zhí)行時(shí),已經(jīng)在激活對(duì)象中定義了foo。
  • Foo被聲明兩次,為什么foo顯示為function而不是undefined或string?
    • 即使foo被聲明兩次,我們通過(guò)創(chuàng)建階段知道函數(shù)在變量之前就被創(chuàng)建在激活對(duì)象上了,而且如果激活對(duì)象上已經(jīng)存在了屬性名稱(chēng),我們只是繞過(guò)了聲明這一步驟。
    • 因此,首先在激活對(duì)象上創(chuàng)建對(duì)函數(shù)foo()的引用,并且當(dāng)解釋器到達(dá)var foo時(shí),我們已經(jīng)看到屬性名稱(chēng)foo存在,因此代碼不執(zhí)行任何操作并繼續(xù)處理。
  • 為什么bar未定義?
    • bar實(shí)際上是一個(gè)具有函數(shù)賦值的變量,我們知道變量是在創(chuàng)建階段被創(chuàng)建的,但它們是使用undefined值初始化的。

總結(jié)

希望到這里你已經(jīng)能夠很好地掌握了JavaScript解釋器如何預(yù)處理你的代碼。 理解執(zhí)行上下文和堆棧可以讓你了解背后的原因:為什么代碼預(yù)處理后的值和你預(yù)期的不一樣。

你認(rèn)為學(xué)習(xí)解釋器的內(nèi)部工作原理是多此一舉還是非常必要的呢? 了解執(zhí)行上下文階段是否能夠幫你你寫(xiě)出更好的JavaScript呢?

以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 一文搞懂JavaScript如何實(shí)現(xiàn)圖片懶加載

    一文搞懂JavaScript如何實(shí)現(xiàn)圖片懶加載

    圖片懶加載,往往作為減少首頁(yè)白屏?xí)r間的一個(gè)解決方案而出現(xiàn)。本文將通過(guò)示例帶大家一起探究一下JavaScript是如何實(shí)現(xiàn)圖片懶加載的,感興趣的可以了解一下
    2022-06-06
  • angular-tree-component的使用詳解

    angular-tree-component的使用詳解

    這篇文章主要介紹了angular-tree-component的使用詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-07-07
  • JavaScript缺少insertAfter解決方案

    JavaScript缺少insertAfter解決方案

    這篇文章主要介紹了JavaScript缺少insertAfter解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-07-07
  • asp.net下利用js實(shí)現(xiàn)返回上一頁(yè)的實(shí)現(xiàn)方法小集

    asp.net下利用js實(shí)現(xiàn)返回上一頁(yè)的實(shí)現(xiàn)方法小集

    其實(shí)要實(shí)現(xiàn)這個(gè)功能主要還是要用到j(luò)avascript
    2009-11-11
  • 小程序使用wxs解決wxml保留2位小數(shù)問(wèn)題

    小程序使用wxs解決wxml保留2位小數(shù)問(wèn)題

    這篇文章主要介紹了小程序使用wxs解決wxml保留2位小數(shù)問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-12-12
  • A標(biāo)簽中通過(guò)href和onclick傳遞的this對(duì)象實(shí)現(xiàn)思路

    A標(biāo)簽中通過(guò)href和onclick傳遞的this對(duì)象實(shí)現(xiàn)思路

    想傳遞當(dāng)前對(duì)象給一個(gè)函數(shù),于是就將這個(gè)URL寫(xiě)成"Javascript:shoControlSidebar(this)",可是結(jié)果發(fā)現(xiàn)這并不可行,接下來(lái)為大家詳細(xì)介紹下解決方法
    2013-04-04
  • JS實(shí)現(xiàn)的簡(jiǎn)易拖放效果示例

    JS實(shí)現(xiàn)的簡(jiǎn)易拖放效果示例

    這篇文章主要介紹了JS實(shí)現(xiàn)的簡(jiǎn)易拖放效果的方法,涉及JS事件監(jiān)聽(tīng)、擴(kuò)展及頁(yè)面元素動(dòng)態(tài)操作的相關(guān)技巧,需要的朋友可以參考下
    2016-12-12
  • JavaScript實(shí)現(xiàn)拖拽盒子效果

    JavaScript實(shí)現(xiàn)拖拽盒子效果

    這篇文章主要為大家詳細(xì)介紹了JavaScript實(shí)現(xiàn)拖拽盒子效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2020-02-02
  • 使用pjax實(shí)現(xiàn)無(wú)刷新更改頁(yè)面url

    使用pjax實(shí)現(xiàn)無(wú)刷新更改頁(yè)面url

    pjax=pushState+ajax,相信用過(guò)github的同學(xué)都知道,github部分頁(yè)面采用了pjax這個(gè)項(xiàng)目來(lái)實(shí)現(xiàn)ajax無(wú)刷新加載的同時(shí)改變頁(yè)面url。一起來(lái)學(xué)習(xí)一下這個(gè)插件吧。
    2015-02-02
  • HTML5之WebSocket入門(mén)3 -通信模型socket.io

    HTML5之WebSocket入門(mén)3 -通信模型socket.io

    socket.io能為程序員提供客戶(hù)端和服務(wù)端一致的編程體驗(yàn),socket.io支持任何的瀏覽器,任何的Mobile設(shè)備。下面通過(guò)本篇文章給大家講解HTML5之WebSocket入門(mén)3 -通信模型socket.io,需要的朋友可以參考下
    2015-08-08

最新評(píng)論