摘要:中的執(zhí)行環(huán)境與堆疊在這篇筆記中我將會深入的探討底層中的一些觀念,其中最重要的就是執(zhí)行環(huán)境。其他執(zhí)行環(huán)境都可以存取全域的東西。在這個(gè)階段直譯器會建立,透過掃描函式傳入的參數(shù),內(nèi)部的函式宣告,變數(shù)宣告。
Javascript 中的執(zhí)行環(huán)境與堆疊
在這篇筆記中我將會深入的探討 JS 底層中的一些觀念,其中最重要的就是執(zhí)行環(huán)境(Execution Context)。當(dāng)您閱讀完這篇文章後您可能會比較清楚關(guān)於直譯器的運(yùn)作方式,明白為什麼有些 函式 變數(shù) 可以在他們被宣告之前就拿來使用,以及這些值是怎麼決定的。
什麼是執(zhí)行環(huán)境?我們說當(dāng) JS 開始執(zhí)行的時(shí)候,這段程式碼必須被執(zhí)行在下面三種環(huán)境之一。
全域 Global:預(yù)設(shè)當(dāng)您程式開始執(zhí)行時(shí)的環(huán)境
函式:當(dāng)我們進(jìn)入一個(gè)函式 function 時(shí)的環(huán)境,也就是開始跑函式內(nèi)部程式碼的時(shí)候
Eval:把一串字串,當(dāng)作指令來執(zhí)行時(shí)的環(huán)境
也就是說一段 JS 程式碼只能存在在上面這三種狀態(tài)或類型。
讓我們直接來看看程式碼
// Global context, JS 最外層的程式碼部分屬於全域 var greeting = "Hi"; function person() { // 從大括號開始到結(jié)束進(jìn)入另外一個(gè)執(zhí)行環(huán)境 var _firstName = "andy"; var _lastName = "you"; function firstName() { // 另外一個(gè)執(zhí)行環(huán)境 return _firstName; } function lastName() { // 執(zhí)行環(huán)境 return _lastName; } alert(greeting + firstName() + " " + lastName()); }
上面這段範(fàn)例沒什麼特別的,我們就是有了一個(gè)全域的執(zhí)行環(huán)境即 global context ,和 3 個(gè) function context,唯一稍微要注意的是 global context 只會有一個(gè)。其他執(zhí)行環(huán)境都可以存取全域的東西。
當(dāng)然您可以有多個(gè) function context 每一個(gè) function 執(zhí)行的時(shí)候就會建立一個(gè)新的 context ,OK!不管講執(zhí)行環(huán)境或者 context 都好抽象,那我們就先把他們當(dāng)作是一個(gè) context 物件,那這個(gè) context 物件講白了就是表示一個(gè)環(huán)境,一個(gè)範(fàn)圍,一個(gè)狀態(tài)。它會建立一個(gè)範(fàn)圍一個(gè)自己特有的領(lǐng)域,任何在 function 裡面宣告的變數(shù)或其他東西都不能被外面直接存取。
如果這樣還不能理解,那我們換個(gè)角度來想這件事,你把 context 當(dāng)成是一張記錄表格,當(dāng)我開始在 global 執(zhí)行程式碼的時(shí)候。
任何變數(shù),function 都會被記載 global 表 上,但是當(dāng)執(zhí)行到 function 內(nèi)部的時(shí)候,此時(shí)會在開出另外一張 function 表 負(fù)責(zé)記錄 function 內(nèi)部的變數(shù)等等。
不過我個(gè)人認(rèn)為 執(zhí)行環(huán)境 是最貼切的翻譯,當(dāng)我在全域這個(gè)環(huán)境時(shí)我能夠取得的變數(shù)和進(jìn)到另外一個(gè) function 環(huán)境時(shí)可能會有不一樣的狀況。
因此在第一小節(jié)我們就下個(gè)小結(jié)論那就是每一段 JS 在運(yùn)行的時(shí)候會根據(jù)片段程式碼所在的區(qū)塊有其特有的 環(huán)境
執(zhí)行環(huán)境的堆疊對於執(zhí)行環(huán)境有了初步的概念之後我們還得知道 - 瀏覽器的 JS 直譯器通常是單執(zhí)行緒的,意味著一次只能夠做一件事。
也就是說當(dāng)一個(gè)事件被執(zhí)行的時(shí)候其他的任務(wù),事件等等就會被丟到執(zhí)行佇列中。這個(gè)東西我們就叫做執(zhí)行堆疊
我們已經(jīng)知道當(dāng) JS 開始跑的時(shí)候一開始會進(jìn)入 global 執(zhí)行環(huán)境,如果您在 global 環(huán)境中呼叫了一個(gè) function A (即: A();),這個(gè)時(shí)候就會建立新的 執(zhí)行環(huán)境 然後這個(gè)新的執(zhí)行環(huán)境會被放到執(zhí)行堆疊的最上面,同樣的如果你現(xiàn)在在 function A 裡面又叫了 function B 那麼就又會在建立一個(gè)執(zhí)行環(huán)境一樣放到執(zhí)行堆疊的最上方,瀏覽器永遠(yuǎn)會先處理堆疊上最上面的執(zhí)行環(huán)境,一旦執(zhí)行環(huán)境裡面的任務(wù)都執(zhí)行完了那它就會被移掉換下一個(gè)
OK 這邊交代得有點(diǎn)亂,我們看到的程式碼的時(shí)候通常最小的執(zhí)行單位就是那一句一句的 statement 語句,一個(gè)語句交代了程式該做一件事。這些 statement 都會有自己的環(huán)境,也因此我們可以把環(huán)境在當(dāng)作一個(gè)上層單位。一個(gè) context 裡面勢必存在一些任務(wù)(語句)。就把一個(gè) context 想像成某個(gè)任務(wù)好了。看看下面的範(fàn)例可能比較有感覺
(function foo(i) { if (i === 3) { return; } else { foo(++i); } }(0));
這段程式碼簡單的呼叫自己三次每一次把參數(shù)加一,每當(dāng) foo 被呼叫的時(shí)候新的 執(zhí)行環(huán)境 就被建立,然後當(dāng) 執(zhí)行環(huán)境 裡面的程式跑完的時(shí)候,就從堆疊中把 執(zhí)行環(huán)境 拿掉,把控制權(quán)交還給上一個(gè)環(huán)境一直到回到 global 為止。
關(guān)於執(zhí)行環(huán)境有 5 個(gè)重點(diǎn)要牢記在心單執(zhí)行緒
同步執(zhí)行
只有一個(gè) global context
function context 沒有限制
就算是自己呼叫自己只要 call function 就會建立執(zhí)行環(huán)境
詳解執(zhí)行環(huán)境所以我們現(xiàn)在知道了每一次 call function 的時(shí)候就會建立一個(gè)新的執(zhí)行環(huán)境,然而在 JS 直譯器內(nèi)部每次調(diào)用一個(gè)執(zhí)行環(huán)境都會有兩個(gè)階段
建立階段 當(dāng) function 被呼叫了但在開始執(zhí)行內(nèi)部程式碼之前
建立一個(gè) scope chain 作用域鍊
建立變數(shù),function,和參數(shù)
設(shè)定 this 的值
執(zhí)行階段
賦值,設(shè)定 function 的參考和解譯執(zhí)行程式碼
概念上我們可以把一個(gè) 執(zhí)行環(huán)境 想像成一個(gè)物件,那麼這個(gè)物件大概會有三個(gè)屬性如下
executionContextObject = { scopeChain: { /* 變數(shù)物件 + 所有父代執(zhí)行環(huán)境物件的變數(shù)物件*/}, variableObject: {/* 函式的參數(shù)/引數(shù),內(nèi)部的變數(shù)和函式*/ }, this: {} }
Activation / Variable Object [AO/VO]Variable Object 變數(shù)物件:根據(jù) ECMA-262 的說明,每一個(gè)執(zhí)行環(huán)境會有一個(gè)與相關(guān)連的變數(shù)物件,這個(gè)物件負(fù)責(zé)記錄執(zhí)行環(huán)境中定義的變數(shù)和函式。
這一個(gè)執(zhí)行環(huán)境物件在 function 被調(diào)用的時(shí)候建立,不過在實(shí)際的 function 被執(zhí)行之前,這就是上面提到的階段 1 - 建立階段。在這個(gè)階段直譯器會建立 executionObject ,透過掃描函式傳入的參數(shù),內(nèi)部的函式宣告,變數(shù)宣告。結(jié)果會被記錄在executionObject 的 變數(shù)物件 variableObject 中。
這裏我們大致模擬直譯器是如何執(zhí)行的流程尋找呼叫 function 的程式碼
在執(zhí)行 function 之前建立 執(zhí)行環(huán)境
進(jìn)入 建立階段
初始化 scope chain
建立 variable object:
建立 arguments object 檢查執(zhí)行環(huán)境的參數(shù),初始化參數(shù)的名稱,值以及建立參考
掃描 function 的宣告
根據(jù)找到的每一個(gè) function 在 variable object 建立,在這邊其實(shí)就是建立 function 名稱在記憶體中的參考指標(biāo)
如果 function 名稱已經(jīng)存在那麼指標(biāo)就會被覆寫
掃描執(zhí)行環(huán)境裡的變數(shù)
每一個(gè)變數(shù)的宣告都會被加入 variable object 的屬性中,並且初始化為 undefined,注意在這個(gè)階段並不會賦值
如果變數(shù)名稱存在就略過,繼續(xù)處理下一個(gè)變數(shù)
判斷決定 this 的值
執(zhí)行階段
執(zhí)行程式碼,賦值,一行一行跑
function foo(i) { var a = "hello"; var b = function B() { }; function c() { } } foo(22);
此時(shí)在建立階段我們就會得到如下的範(fàn)例
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: undefined, b: undefined }, this: { ... } }
如您所見,在建立階段處理關(guān)於定義宣告的部分,此時(shí)並不會賦值,所以 function b 並沒有被參考。不過參數(shù)是唯一的例外,此時(shí)參數(shù)的值已經(jīng)被建立。一旦建立階段完成,剩下的流程就是開始執(zhí)行階段,當(dāng)執(zhí)行階段完成的時(shí)候執(zhí)行環(huán)境就會如下
fooExecutionContext = { scopeChain: { ... }, variableObject: { arguments: { 0: 22, length: 1 }, i: 22, c: pointer to function c() a: "hello", b: pointer to function B() }, this: { ... } }變數(shù)宣告提升
您可以找到很多關(guān)於定義 Javascript hoisting 的資料,他們通常會解釋這就是一種把宣告提升到其所在區(qū)域內(nèi)頂端的行為,然而這樣並沒有解釋到細(xì)節(jié),為什麼會發(fā)生這件事,不過呢剛剛您已經(jīng)知道了關(guān)於整個(gè)直譯器解意的流程,現(xiàn)在您可以很清楚的明白為什麼會這樣了。
(function () { console.log("foo: " + typeof foo); // function pointer console.log("bar: " + typeof bar); // undefined var foo = "hello", bar = function() { return "world"; }; function foo() { return "hello"; } }());
現(xiàn)在我們可以回答關(guān)於上面這段程式碼的一些問題
為什麼我們在宣告之前可以存取 foo
如果我們看看 建立階段 的流程我們可以知道變數(shù)在這個(gè)時(shí)期早就被建立了
Foo 被宣告 2 次,為什麼 foo 是 function 而不是 undefined 或 string?
即使 foo 宣告了2次,我們知道在建立階段 function 會先被建立。因此變數(shù)已經(jīng)存在了在這個(gè)階段 string 不會被賦予 foo
因此在真正執(zhí)行 function 之前 foo 是會先被建立,等他真正跑完執(zhí)行階段的時(shí)候 foo 才會被覆寫成 "hello"
為什麼 bar 是 undefined ?
bar 就只是一個(gè)變數(shù),在這個(gè)階段並還沒賦值所以就是 undefined
總結(jié)下個(gè)收斂的結(jié)論就是
每一個(gè)片段程式碼都會屬於某個(gè)執(zhí)行環(huán)境,或者說在開始執(zhí)行程式碼之前會先建立 執(zhí)行環(huán)境
執(zhí)行環(huán)境比喻來說就像是一個(gè)物件負(fù)責(zé)紀(jì)錄這個(gè) 環(huán)境 下相關(guān)的事物 變數(shù) function 等等
從上往下看這個(gè)執(zhí)行環(huán)境物件最重要的是 scope chain, variable object, this 這三個(gè)屬性
variable object 才是實(shí)際上記錄變數(shù),function,arguments 的地方
另外一個(gè)重要的點(diǎn)是 scope chain 他負(fù)責(zé)記錄每個(gè)環(huán)境之間切換的關(guān)聯(lián),例如從 global -> a()
每次開始建立執(zhí)行環(huán)境的時(shí)候就會分成兩個(gè)階段
開始建立執(zhí)行環(huán)境的時(shí)間點(diǎn)是在 function 被呼叫後,實(shí)際執(zhí)行內(nèi)部程式碼前
建立階段,初始化這個(gè)環(huán)境,除了 arguments 外其他都只是先定義變數(shù),函式指標(biāo),並沒有賦值
執(zhí)行階段,開始一行一行執(zhí)行,賦值
希望現(xiàn)在您可以更清楚關(guān)於 Javascript 如何運(yùn)行您的程式碼,瞭解執(zhí)行環(huán)境,堆疊可以讓您更清楚您的程式碼在不同狀態(tài)下取到的值,如此一來相信您在組織 JS 的時(shí)候會有更好的寫法。
0
1
2
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/78758.html
摘要:不過到底是怎麼保留的另外為什麼一個(gè)閉包可以一直使用區(qū)域變數(shù),即便這些變數(shù)在該內(nèi)已經(jīng)不存在了為了解開閉包的神秘面紗,我們將要假裝沒有閉包這東西而且也不能夠用嵌套來重新實(shí)作閉包。 原文出處: 連結(jié) 話說網(wǎng)路上有很多文章在探討閉包(Closures)時(shí)大多都是簡單的帶過。大多的都將閉包的定義濃縮成一句簡單的解釋,那就是一個(gè)閉包是一個(gè)函數(shù)能夠保留其建立時(shí)的執(zhí)行環(huán)境。不過到底是怎麼保留的? 另外...
摘要:的架構(gòu)設(shè)計(jì)促使第三方開發(fā)者讓核心發(fā)揮出無限的潛力。當(dāng)然建置比起開發(fā)是較進(jìn)階的議題,因?yàn)槲覀儽仨氁斫鈨?nèi)部的一些事件。這個(gè)編譯結(jié)果包含的訊息包含模組的狀態(tài),編譯後的資源檔,發(fā)生異動的檔案,被觀察的相依套件等。 本文將對 webpack 周邊的 middleware 與 plugin 套件等作些介紹,若您對於 webpack 還不了解可以參考這篇彙整的翻譯。 webpack dev ser...
摘要:原文出處持續(xù)整合持續(xù)交付這篇文章將一步一步介紹如何使用與來完成持續(xù)整合與持續(xù)交付的開發(fā)流程。前言什麼是持續(xù)整合持續(xù)交付持續(xù)整合持續(xù)交付,簡稱,具體介紹可以參考山姆鍋對持續(xù)整合持續(xù)部署持續(xù)交付的定義這篇文章。 原文出處:DevOps:持續(xù)整合&持續(xù)交付(Docker、CircleCI、AWS) showImg(https://segmentfault.com/img/bVlxh...
摘要:目錄許多開發(fā)者會把的目錄命名為但這並不強(qiáng)迫。所有的檔案都會使用從被編譯成。同時(shí)有個(gè)小小的重點(diǎn)那就是我們可已觀察編譯後的檔案大小。在專案目錄下執(zhí)行可以觀察截至目前為止的結(jié)果。我們的目標(biāo)是要把編譯封裝到我們的中。 在今時(shí)今日,webpack 已經(jīng)成為前端開發(fā)非常重要的工具之一。本質(zhì)上它是一個(gè) Javascript 模組封裝工具,但透過 loaders 和 plugins 它也可以轉(zhuǎn)換封裝其...
摘要:參考令人困惑的地方項(xiàng)目名稱項(xiàng)目名稱版本描述作者開源協(xié)議主文件指定了運(yùn)行腳本命令的命令行縮寫,比如這是的指定了運(yùn)行時(shí),所要執(zhí)行的命令。要解析并且完成相應(yīng)的功能,這些基本都是必須的。 參考 Webpack——令人困惑的地方 package.json { name: 項(xiàng)目名稱, //項(xiàng)目名稱 version: 1.0.0, //版本 description: vue+...
閱讀 1759·2021-11-11 16:55
閱讀 2545·2021-08-27 13:11
閱讀 3622·2019-08-30 15:53
閱讀 2301·2019-08-30 15:44
閱讀 1378·2019-08-30 11:20
閱讀 1036·2019-08-30 10:55
閱讀 943·2019-08-29 18:40
閱讀 3029·2019-08-29 16:13