var a = 'heihei', b = 'xixi'
function foo () {
console.log(a)
}
function bar () {
var a = 'houhou'
foo()
console.log(b)
}
bar()
// heihei
// xixi
複製代碼
若是您很快就能得出上述結果,那相信您的功底很是之紮實,若是沒法肯定,那麼這篇文章通讀以後,相信能夠幫您解疑。javascript
JS中可執行代碼有三種:全局代碼
,函數代碼
,eval代碼
。代碼執行前須要準備的執行環境
也稱爲執行上下文
,因此也分爲全局執行上下文
,函數執行上下文
,eval執行上下文
。html
全局執行上下文中的變量對象就是全局對象,預置了不少屬性和函數。在瀏覽器中是window
,在NodeJS中是global
前端
[[scope]]
屬性建立做用域鏈(下面會講到)arguments
建立活動對象
變量對象
被激活爲激活對象
,此時發生"hoist"
聲明提高java
函數的全部形參git
arguments
,其值也是一個對象(類數組對象,有length
屬性),按形參順序賦值,值爲實參undefined
函數聲明github
Function
對象的引用。變量聲明segmentfault
undefined
)組成一個變量對象的屬性被建立;
注意
: 整個過程能夠大概描述成:函數的形參=>函數聲明=>變量聲明
, 其中在建立函數聲明時,若是名字存在,則會被重寫,在建立變量時,若是變量名存在,則忽略不會進行任何操做。數組
根據代碼修改激活對象對象中對應的值。若是當前執行上下文中的變量對象沒有該屬性,就去父級的執行上下文變量對象中尋找,直至到全局執行上下文。找不到就報錯瀏覽器
eval執行上下文比較特殊,它取決於eval函數是直接調用仍是間接調用。 引用MDN上的說法:bash
若是間接的使用
eval()
,好比經過一個引用來調用它,而不是直接的調用eval
。 從 ECMAScript 5 起,它工做在全局做用域下,而不是局部做用域中。
eval
執行上下文爲執行時所處的執行上下文,具備和這個執行上文相同的做用域eval
執行上下文爲全局執行上下文function foo () {
var x = 2, y = 4
console.log(eval('x + y')) // 直接調用,執行上下文爲當前函數執行上下文,結果是 6
var geval = eval // 等價於在全局做用域調用
console.log(geval('x + y')) // 間接調用,執行上下文爲全局執行上下文,x is not defined,實際上y也是not defined
console.log(window.eval('x + y')) // 這也是間接調用
}
foo()
複製代碼
JS經過執行上下文棧
來管理上述這些執行上下文
。
globalContext
表示它,而且只有當整個應用程序結束的時候,ECStack
纔會被清空,因此程序結束以前,ECStack
最底部永遠有個globalContext
globalContext
function foo (a) {
console.log(a)
}
function bar (b) {
foo(b)
}
bar('hehe')
複製代碼
變量對象
(variable object, VO):每一個執行上下文都一個與之對應的變量對象,它是與執行上下文相關的數據做用域,存儲了在上下文中的函數標識符、形參、變量聲明等。但在規範上或者引擎實現上,這個對象是不能在JS環境中訪問的激活對象
(activation object, AO):當進入某個函數執行上下文中
時,其對應變量對象會被激活,變量對象上的屬性才能被訪問,因此稱之爲激活對象。
激活對象
就是在函數執行上下文中被激活成可訪問的變量對象
。
根據詞法環境規範定義:
- A Lexical Environment is a specification type used to define the association of Identifiers to specific variables and functions based upon the lexical nesting structure of ECMAScript code. A Lexical Environment consists of an Environment Record and a possibly null reference to an outer Lexical Environment.
- 詞法環境是一種規範類型,基於 ECMAScript 代碼的詞法嵌套結構來定義標識符與特定變量和函數的關聯關係。詞法環境由
環境記錄
(environment record)和可能爲空引用(null)的外部詞法環境
組成。
環境記錄
:主要是聲明性環境記錄
(declarative Environment Records)和對象環境記錄
(object Environment Records),其次還有全局環境記錄
(global Environment Records)和函數環境記錄
(function Environment Records)。
聲明性環境記錄
(declarative Environment Records):存儲變量、函數和參數, 用於函數聲明、變量聲明和catch語句。
對象環境記錄
(object Environment Records):用於像with這樣綁定對象標識符(做用域)的語句。
全局環境記錄
和函數環境記錄
:是特殊的聲明性環境記錄,形式上可理解爲對應的變量對象。
外部詞法環境
:構成做用域鏈的關鍵
- 詞法做用域(靜態做用域):函數的做用域在函數定義的時候就決定了。JS使用的是詞法做用域。
- 動態做用域:函數的做用域是在函數調用的時候才決定的。
做用域(Scope)用於規定如何查找變量,也就是肯定當前執行上下文中對變量的訪問權限。
在函數中有一個內部屬性,當函數建立的時候,就會保存全部父變量對象到其中,在查找變量值的時候,會先從[[scope]]
頂部即當前上下文的變量對象(做用域)中查找,若是沒有找到,就會根據當前執行上下文中的[[scope]]
對外部執行環境的引用順序,從父級(詞法層面上的父級)執行上下文的變量對象中查找,一直找到全局上下文的變量對象,也就是全局對象。這樣由多個執行上下文的變量對象構成的鏈表就叫作做用域鏈
。
注意
:
- 當進入函數執行上下文(函數激活)時,會將該函數的變量對象推入到做用域鏈前端。
- 正式因爲做用域與做用域鏈的這種關係,在當前函數執行上下文的活動對象中一定存在this和arguments,因此this和arguments的搜索在當前執行執行上下文就中止了。
回過頭來看前言中的問題,按照下面的流程進行(只列出關鍵部分):
ECStack = [
globalContext
]
複製代碼
globalContext = {
VO: [global], // 指向全局對象
Scope: [globalContext.VO], // 可訪問權限
this: globalContext.VO
}
複製代碼
foo
函數和bar
函數被建立,生成內部做用域鏈。foo.[[scope]] = [
globalContext.VO
]
bar.[[scope]] = [
globalContext.VO
]
複製代碼
2.2節
中所述,執行bar
函數前,建立bar
函數執行上下文,並推入執行上下文棧中。ECStack = [
barContext,
globalContext
]
複製代碼
bar
函數執行上下文barContext = {
AO: {
arguments: {
length: 0
},
a: undefined,
foo: <reference to function foo() {}>
},
Scope: [barContext.AO, globalContext.VO],
this: undefined
}
複製代碼
bar
函數執行,開始執行foo
函數,同理,建立foo
函數執行上下文,並推入執行上下文棧中ECStack = [
fooContext,
barContext,
globalContext
]
複製代碼
foo
函數執行上下文fooContext = {
AO: {
arguments: {
length: 0
}
},
Scope: [fooContext.AO, globalContext.VO],
this: undefined
}
複製代碼
foo
函數執行,foo
函數執行上下文中的激活對象沒有屬性a
,因此沿着做用域鏈[[scope]]
找到全局執行上下文中的變量對象,其指向全局對象,故輸出'heihei'
。執行完畢彈出foo
函數執行上下文並銷燬。ECStack = [
barContext,
globalContext
]
複製代碼
bar
函數執行,bar
函數執行上下文中的激活對象沒有屬性b
,因此沿着做用域鏈[[scope]]
找到全局執行上下文中的變量對象,其指向全局對象,故輸出'xixi'
。執行完畢彈出bar
函數執行上下文並銷燬。ECStack = [
globalContext
]
複製代碼