1. 程式人生 > >js基礎知識(4)-執行上下文

js基礎知識(4)-執行上下文

 

當執行 JS 程式碼時,會產生三種執行上下文
• 全域性執行上下文
• 函式執行上下文
• eval 執行上下文

當瀏覽器首次載入你的指令碼,它將預設進入全域性執行上下文。如果,你在你的全域性程式碼中呼叫一個函式,你程式的時序將進入被呼叫的函式,並建立一個新的執行上下文,並將新建立的上下文壓入執行棧的頂部。
如果你呼叫當前函式內部的其他函式,相同的事情會在此上演。程式碼的執行流程進入內部函式,建立一個新的執行上下文並把它壓入執行棧的頂部。瀏覽器總會執行位於棧頂的執行上下文,一旦當前上下文函式執行結束,它將被從棧頂彈出,並將上下文控制權交給當前的棧。這樣,堆疊中的上下文就會被依次執行並且彈出堆疊,直到回到全域性的上下文。

每個執行上下文中都有三個重要的屬性 

(1)VO(變數物件)或者AO(活動物件),包含變數、函式宣告和函式的形參(AOVO只能有一個)
(2)作用域鏈(scope)(JS 採用詞法作用域,也就是說變數的作用域是在定義時就決定了)
(3)this

VO(變數物件)/AO(活動物件)

變數物件(Variable object)是說JS的執行上下文中都有個物件用來存放執行上下文中可被訪問但是不能被delete的函式標示符、形參、變數宣告等。它們會被掛在這個物件上,物件的屬性對應它們的名字物件屬性的值對應它們的值但這個物件是規範上或者說是引擎實現上的不可在JS環境中訪問到活動物件

啟用物件(Activation object):有了變數物件存每個上下文中的東西,但是它什麼時候能被訪問到呢?就是每進入一個執行上下文時,這個執行上下文中的變數物件就被啟用,也就是該上下文中的函式標示符、形參、變數宣告等就可以被訪問到了

var a = 10
function foo(i) {
var b = 20
}
foo()

對於上述程式碼,執行棧中有兩個上下文:全域性上下文和函式 foo 上下文。
 

stack = [
globalContext,
fooContext
]

對於全域性上下文來說,VO 大概是這樣的
 

globalContext.VO === globe
globalContext.VO = {
a: undefned,
foo: <Function>,
}

對於函式 foo 來說,VO 不能訪問,只能訪問到活動物件(AO)
 

fooContext.VO === foo.AO
fooContext.AO {
i: undefned,
b: undefned,
arguments: <>
}
// arguments 是函式獨有的物件(箭頭函式沒有)
// 該物件是一個偽陣列,有 `length` 屬性且可以通過下標訪問元素
// 該物件中的 `callee` 屬性代表函式本身
// `caller` 屬性代表函式的呼叫者

對於作用域鏈,可以把它理解成包含自身變數物件和上級變數物件的列表,通過[[Scope]] 屬性查詢上級變數
 

fooContext.[[Scope]] = [
globalContext.VO
]
fooContext.Scope = fooContext.[[Scope]] + fooContext.VO
fooContext.Scope = [
fooContext.VO,
globalContext.VO
]

接下來讓我們看一個老生常談的例子,var
 

b() // call b
console.log(a) // undefned
var a = 'Hello world'
function b() {
console.log('call b')
}

想必以上的輸出大家肯定都已經明白了,這是因為函式和變數提升的原因。通常提升的解釋是說將宣告的程式碼移動到了頂部,這其實沒有什麼錯誤,便於大家理解。但是更準確的解釋應該是:在生成執行上下文時,會有兩個階段。第一個階段是建立的階段(具體步驟是建立 VO),JS 直譯器會找出需要提升的變數和函式,並且給他們提前在記憶體中開闢好空間,函式的話會將整個函式存入記憶體中,變數只宣告並且賦值為undefined,所以在第二個階段,也就是程式碼執行階段,我們可以直接提前使用。在提升的過程中,相同的函式會覆蓋上一個函式,並且函式優先於變數提升