JavaScript中執行環境和棧

來源:互聯網
上載者:User

標籤:

 

在這篇文章中,我會深入理解JavaScript最根本的組成之一 : "執行環境(執行內容)"。文章結束後,你應該對解譯器試圖做什麼,為什麼一些函數/變數在未聲明時就可以調用並且他們的值是如何確定的有一個清晰的認識。

 

什麼是執行環境(執行內容)
當代碼在JavaScript中啟動並執行時候,代碼在環境中被執行是非常重要的,它會被評估為以下之一類型來運行:
全域代碼:預設環境,你的代碼第一時間在這兒運行。
函數代碼:當執行流進入一個函數體的時候。
Eval代碼:在eval()函數中的文本。

你可以在網上尋找關於範圍的大量資料,這篇文章的目的就是讓事情變得更容易理解。讓我們把執行環境作為環境/範圍,當前代碼被評估在這個環境/範圍中。現在,讓我們來看一個例子,代碼被評估某個類型,這個例子中類型包括全域和函數環境:

這裡並沒有什麼特別的,我們有一個全域環境,全域環境由紫色邊框表示,還有三個不同的函數環境分別由綠色邊框,藍色邊框和橙色邊框表示。這裡只能由一個全域環境,在你的程式中,全域環境可以被其他環境訪問。

你可以由很多的函數環境,每個函數都會建立一個新的函數環境,在新的函數環境中,會建立一個私人範圍,在這個函數中建立的任何聲明都不能被當前函數範圍之外的地方訪問。在上面例子中,一個函數可以訪問當前環境外部定義的變數,但是在外部卻無法訪問函數內部聲明的變數。為什麼這樣?這段代碼究竟是如何評估的?


 執行環境棧

JavaScript解譯器在瀏覽器中是單線程的,這意味著瀏覽器在同一時間內只執行一個事件,對於其他的事件我們把它們排隊在一個稱為 執行棧的地方。下表是一個單線程棧的抽象視圖。

我們已經知道,當瀏覽器第一次載入你的script,它預設的進了全域執行環境。如果在你的全域代碼中你調用了一個函數,那麼順序流就會進入到你調用的函數當中,建立一個新的執行環境並且把這個環境添加到執行棧的頂部。

如果你在當前的函數中調用了其他函數,同樣的事會再次發生。執行流進入內建函式,並且建立一個新的執行環境,把它添加到已經存在的執行棧的頂部。瀏覽器始終執行當前在棧頂部的執行環境。一旦函數完成了當前的執行環境,它就會被彈出棧的頂部, 把控制權返回給當前執行環境的下個執行環境。下面例子展示了一個遞迴函式和該程式的執行棧:
```javascript
(function foo(i) {
if (i === 3) {
return;
}
else {
foo(++i);
}
}(0));
```


這段代碼簡單地調用了自己三次,由1遞增i的值。每次函數foo被調用,一個新的執行環境就會被調用。一旦一個環境完成了執行,它就會被彈出執行棧並且把控制權返回給當前執行環境的下個執行環境直到再次到達全域執行環境。

記住執行棧,這兒有五個關鍵點 
1. 單線程
2. 同步執行
3. 一個全域環境
4. 無限的函數環境
5. 函數被調用就會建立一個新的執行環境,甚至調用自己。


 執行環境的詳情

現在我們直到,一個函數被調用就會建立一個新的執行環境。然而解譯器的內部,每次調用執行環境會有兩個階段:

1. 建立階段
- 當函數被調用,但是為執行內部代碼之前:
- 建立一個[範圍鏈](http://davidshariff.com/blog/javascript-scope-chain-and-closures/)。
- 建立變數,函數和參數。
- 確定this的值。

2. 啟用/代碼執行階段
> - 賦值,引用函數,解釋/執行代碼。

這可能意味著每個執行環境在概念上作為一個對象並帶有三個屬性
```javascript
executionContextObj = {
scopeChain: { /* variableObject + all parent execution context‘s variableObject */ },
//範圍鏈:{變數對象+所有父執行環境的變數對象}
variableObject: { /* function arguments / parameters, inner variable and function declarations */ },
//變數對象:{函數形參+內部的變數+函式宣告(但不包含運算式)}
this: {}
}
```
----
### 活動/變數 對象(AO/VO)
當函數被調用,executionContextObj就被建立,該對象在實際函數執行前就已建立。這就是已知的第一個階段建立階段.在第一階段,解譯器建立了executionContextObj對象,通過掃描函數,傳遞形參,函式宣告和局部變數聲明。掃描的結果成為了變數對象在executionContextObj中。

- 這有一個解譯器是如何評估代碼的偽概述:
1. 找到一些代碼來調用函數
2. 在執行函數代碼前,建立執行環境
3. 進入建立階段:
- 初始化範圍鏈
- 建立變數對象:
- 建立arguments對象,檢查環境中的參數,初始化名和值,建立一個參考副本
- 掃描環境中內的函式宣告:
- 某個函數被發現,在變數對象建立一個屬性,它是函數的確切名。它是一個指標在記憶體中,指向這個函數。
- 如果這個函數名已存在,這個指標的值將會重寫。
- 掃描環境內的變數聲明
- 某個變數聲明被發現,在變數對象中建立一個屬性,他是變數的名,初始化它的值為undefined。
- 如果變數名在變數對象中已存在,什麼也不做,繼續掃描。
- 在環境中確定this的值。
4. 啟用/代碼執行階段:在當前上下文上運行/解釋函數代碼,並隨著代碼一行行執行指派變數的值

看下面例子:
```javascript
function foo(i) {
var a = ‘hello‘;
var b = function privateB() {

};
function c() {

}
}

foo(22);
```
On calling foo(22), the creation stage looks as follows:
在調用foo(22)時,建立階段像下面這樣:
```javascript
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: undefined,
b: undefined
},
this: { ... }
}
```
正如你看到的,建立階段處理了定義屬性的名,但是並不把值賦給變數,不包括形參和實參。一旦建立階段完成,執行流進入函數並且啟用/代碼執行階段,在函數執行結束之後,看起來像這樣:
```javascript
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c()
a: ‘hello‘,
b: pointer to function privateB()
},
this: { ... }
}
```
### 進階一言

你可以在網上找到大量的術語來描述JavaScript進階。解釋變數和函式宣告被提升到它們函數範圍的頂端。然而,沒有一個詳細的解釋為什麼這樣, 現在你配備了關於解譯器怎麼建立使用中的物件的新知識,這會很明白這是為什麼。看看下面例子:
```javascript
?(function() {

console.log(typeof foo); // function pointer
console.log(typeof bar); // undefined

var foo = ‘hello‘,
bar = function() {
return ‘world‘;
};

function foo() {
return ‘hello‘;
}

}());?
```
現在我們能解答的問題有:

為什麼在聲明foo之前我們就可以調用?
如果我們按照建立階段進行,我們知道變數在啟用/執行階段之前已經被建立了。因此,在函數流開始執行,foo已經在使用中的物件中被定義了。
foo被聲明了兩次, 為什麼foo展現出來的是functiton,而不是undefined或者string
我們從建立階段知道,儘管foo被聲明了兩次,函數在使用中的物件中是在變數之前被建立的,並且如果屬性名稱在使用中的物件已經存在,我們會簡單地繞過這個聲明。

所以,引用函數foo()是在使用中的物件上第一次被建立的, 當我們解釋到 var foo的時候,我們發現屬性名稱foo已經存在,所以代碼不會做任何處理,只是繼續進行

為什麼bar是undefined?

bar確實是一個變數,並且值是一個函數。我們知道變數是在建立階段被建立的,但是它們的值被初始化為undefined。


總結:
希望現在你能很好的理解JavaScript解譯器是如何評估你的代碼。
瞭解執行環境和棧可以讓你知道你的代碼評估的值為何與你預期值不同的原因。

你認為解譯器內部工作開支太多?或者你應該有必要的JavaScript知識?瞭解了執行環境可能會協助你寫出更好的JavaScript?


[原文地址](http://davidshariff.com/blog/what-is-the-execution-context-in-javascript/)

JavaScript中執行環境和棧

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.