JavaScript
執行環境、作用域鍊
JavaScript
EC
的組成
EC
當代碼執行時,會進入不同的執行上下文,這些執行上下文會形成一個執行上下文棧(
js
,
Execution context stack
)
ECS
js
中,執行環境分為三種:
- 全局執行環境 - 一旦代碼被載入,全局執行環境被建立,在任何地方都可以通路到全局執行環境中的内容
- 局部執行環境 - 當執行某一個函數時,局部執行環境被建立,當函數執行完畢時,該局部執行環境被銷毀,其中的所有内容也被銷毀
-
- 在Eval
函數内運作時會被建立Eval
ECS
:環境棧 – 執行上下文棧
ECS
在執行代碼時,一系列活動的執行上下文從邏輯上形成一個棧。當在全局上下文中調用執行一個函數時,程式流就進入該被調用函數内,此時引擎就會為該函數建立一個新的執行上下文,函數的環境(執行上下文)就會被推入一個該環境棧中,在函數執行完畢後,棧将其環境彈出,把控制權交還給之前的執行環境,直至回到全局的上下文(當應用程式退出後,例如關閉網頁或浏覽器時才會銷毀全局執行環境)環境棧的棧底永遠都是全局上下文,棧頂時目前執行上下文。當在不同上下文環境間切換時,會通過退棧入棧的形式完成。
eg
:
eg
壓棧:全局EC -> 局部EC1 -> 局部EC2 -> 目前EC
出棧:目前EC -> 局部EC2 -> 局部EC1 -> 全局EC
變量對象( VO
)和活動對象( AO
)
VO
AO
- 每一個
都對應一個變量對象
EC
,在該執行上下文中定義的變量、函數以及函數形參
VO
都存放在其中。
arguments
- 如果目前執行上下文為函數,則
(活動對象)會被作為目前執行上下文的變量對象
AO
=
AO
+ 函數形參 +
VO
。
arguments
在最開始隻包含一個變量,即
AO
對象
arguments
下面以 EC
建立的兩個階段來對 AO
和 VO
進行說明
EC
AO
VO
例1:
//1
alert(a);//function
a();//233
var a = 1;
function a () {
console.log(233)
}
alert(a);//1
- 進入執行上下文
ECObject = {
VO: {
a : <reference to FunctionDeclaration "a">
}
}
- 執行階段
ECObject = {
VO: {
a : 1
}
}
例2:
//2
function fun(m, n) {
console.log(a);//function
a();//233
var a = 1;
function a() {
console.log(233)
}
console.log(a);//1
}
fun(1,2)
- 進入執行上下文
ECObject = {
VO: {
arguments: {
callee: fun,
length: 2,
0: 1,
1: 2
},
m: 1,
n: 2,
a : <reference to FunctionDeclaration "a">
}
}
- 執行階段
ECObject = {
VO: {
arguments: {
callee: fun,
length: 2,
0: 1,
1: 2
},
m: 1,
n: 2,
a: 1
}
}
[[Scope]]
屬性 與 Scope
(作用域鍊)
[[Scope]]
Scope
- 在建立函數時,會建立一個預先包含全局變量對象以及所有祖先變量對象的作用域鍊,并将其儲存在
屬性中。當函數被執行時,會将
[[Scope]]
屬性中的對象複制到
[[Scope]]
作用域鍊上,并會建立執行環境的變量對象(活動對象 – 對于函數而言)且将其推入執行環境作用域鍊(
Scope
)的最頂端。
Scope
是一個包含了所有上層變量對象的分層鍊,它是目前函數上下文的靜态屬性,從建立後便不會改變。當目前函數上下文環境被建立時,它便被儲存在其中了,直到目前函數上下文環境被銷毀,否則它一直會存在其中。
[[Scope]]
=
Scope
+
AO|VO
[[Scope]]
中存儲着目前執行環境的作用域鍊。實際上,它是對執行上下文
Scope
中的變量對象
EC
有序通路的連結清單,能夠按照順序通路到
VO|AO
,便能夠通路到其中的變量和屬性。
VO|AO
- 辨別符解析是沿着作用域鍊一級一級地搜尋辨別符的過程。搜尋過程始終從作用域鍊的前端開始,然後逐漸開始向後搜做,直至找到辨別符為止(如果找不到,通常會導緻錯誤發生)。全局執行環境的變量對象始終都是作用域鍊中的最後一個變量對象。
[[Scope]]
、 Scope
、 VO
例子:
[[Scope]]
Scope
VO
let x=10;
function f1(){
let y=20;
function f2(){
return x+y;
}
}
f2
的
[[Scope]]
可以表示如下:
f2.[[Scope]] = [
f1Context.AO,
globalContext.VO
]
f2
所在執行上下文
Scope
可以表示為:
f2.[[Scope]] = [
f2Context.AO,
f1Context.AO,
globalContext.VO
]
由上可知:
Scope
是執行上下文的屬性,
[[Scope]]
是函數的靜态屬性
給出以上提到的對象的值:
-
globalContext.VO
globalContext.VO = {
x:10,
f1:<reference to FunctionDeclaration "f1">
}
-
f1Context.AO
f1Context.AO = {
argumnets: {
callee:f1,
length:0
},
y:20,
f2:<reference to FunctionDeclaration "f2">
}
-
(f1的所有上層f1.[[Scope]]
的EC
)VO
f1.[[scope]] = {
globalContext.VO
}
-
f1.Scope
f1.Scope = {
f1Context.AO,
f1.[[scope]]
}
-
f2Context.VO
f2Context.AO = {
argumnets: {
callee:f2,
length:0
}
}
-
(f2的所有上層f2.[[Scope]]
的EC
)VO
f2.[[scope]] = {
f1Context.VO,
globalContext.VO
}
-
f2.Scope
f2.Scope = {
f2Context.VO,
f2.[[scope]]
}
作用域鍊例子:
function compare(value1,value2){
if(value1<value2){
return -1;
}else if(value1<value2){
return 1;
}else {
return 0;
}
}
var result = compare(1,2);
EC
建立(以下說明都是對函數執行上下文的說明)
EC
EC
的建立分為兩個階段 : 1. 進入執行上下文 2. 執行階段
1. 進入執行上下文
發生在函數調用時,但是在執行代碼之前。掃描器掃描傳遞給函數的參數或 arguments
,函數内的變量和函數聲明,并建立執行執行上下文對象,掃描的結果将完成變量對象的建立
進入執行上下文做的事情:
- 掃描上下文環境中中聲明的形參、函數以及變量,建立變量對象
,并依次填充變量對象的屬性VO
建立變量對象
VO
過程
- 根據函數的形參,建立并初始化
對象:形參作為屬性,實參作為其值。對于沒有實參的形參,值為arguments
(這部分實際就是js的預解析機制,js的預解析機制)undefined
- 掃描函數内部,查找并完成函數聲明:函數的名稱作為其名,值為函數體。如果在變量對象中有同名的屬性/函數,則函數體替代該屬性值/覆寫函數體。
- 掃描函數内部,查找并完成變量聲明:屬性名為變量名,值初始化為
。如果變量名和已經存在的屬性同名,不會影響到同名的值(因為都為初始值,即undefined
)。如果變量名和已經存在的函數同名,則不會替換掉函數的值,即最終該屬性的值為已經存在的函數體undefined
- 函數表達式不會替換同名屬性的值。
- 求出
的值this
2. 執行階段
發生在函數執行時,的一些屬性的
VO
值将會被确定
undefined
參考:
- javascript 執行環境,變量對象,作用域鍊
- JS執行環境、作用域鍊、活動對象
- JS核心原理 - 執行環境
- 了解JS執行環境
- JS進階程式設計
- 高性能JavaScript