JavaScript不一樣於其餘語言,存在變量提高,以下面代碼例子:javascript
console.log(x)
var x = 'hello world';
複製代碼
這段代碼不會報錯,會輸出 undefined
。這就是所謂的變量提高,但具體細節JS引擎是怎麼處理的,還須要理解JS的Execution Context執行上下文。java
Execution Context 是JS執行代碼時候的一個上下文環境。如執行到一個調用函數,就會進入這個函數的執行上下文,執行上下文中會肯定這個函數執行期間用到的諸如this,變量,對象以及定義的方法等。express
當瀏覽器加載script的時候,默認直接進入Global Execution Context(全局上下文),將全局上下文入棧。若是在代碼中調用了函數,則會建立Function Execution Context(函數上下文)並壓入調用棧內,變成當前的執行環境上下文。當執行完該函數,該函數的執行上下文便從調用棧彈出返回到上一個執行上下文。瀏覽器
Global execution context。當js文件加載進瀏覽器運行的時候,進入的就是全局執行上下文。全局變量都是在這個執行上下文中。代碼在任何位置都能訪問。ecmascript
Functional execution context。定義在具體某個方法中的上下文。只有在該方法和該方法中的內部方法中訪問。函數
Eval。定義在Eval方法中的上下文。該方法不建議使用對此就不進一步研究。ui
Js是單線程執行,每次註定只能訪問一個execution context。所以調用棧最上方的執行上下文將最早被執行,執行完後返回到上層的執行上下文繼續執行。引用一篇博文的動態圖示以下:this
execution context期間js引擎主要分兩個階段:spa
建立階段(函數調用時,但在函數執行前)線程
JS解析器掃描一遍代碼,建立execution context內對應的variables, functions和arguments。這三個稱之爲Variable Object。
建立做用域鏈scope chain
決定this的指向
executionContextObj = {
'scopeChain': { /* variableObject + all parent execution context's variableObject */ },
'variableObject': { /* function arguments / parameters, inner variable and function declarations */ },
'this': {}
}
複製代碼
executionContextObj由函數調用時運行前建立,建立階段arguments的參數會直接傳入,函數內部定義的變量會初始化爲undefined。
執行階段
下面是執行上下文期間JS引擎執行僞代碼
一個簡單例子以下:
console.log(foo(22))
console.log(x);
var x = 'hello world';
function foo(i) {
var a = 'hello';
var b = function privateB() {
};
function c() {
}
console.log(i)
}
複製代碼
(a):代碼首先進入到全局上下文的建立階段。
ExecutionContextGlobal = {
scopeChain: {...},
variableObject: {
x: undefined,
foo: pointer to function foo() }, this: {...}
}
複製代碼
而後進入全局執行上下文的執行階段。這一階段從上至下逐條執行代碼,運行到console.log(foo(22))
該行時,建立階段已經爲variableObject中的foo賦值了,所以執行時會執行foo(22)
函數。
當執行foo(22)
函數時,又將進入foo()
的執行上下文,詳見(b)。
當執行到console.log(x)
時,此時x
在variableObject中賦值爲undefined
,所以打印出undefined
,這也正是變量提高產生的結果。
當執行到var x = 'hello world';
,variableObject中的x被賦值爲hello world
。
繼續往下是foo
函數的聲明,所以什麼也不作,執行階段結束。下面是執行階段完成後的ExecutionContextGlobal。
ExecutionContextGlobal = {
scopeChain: {...},
variableObject: {
x: 'hello world',
foo: pointer to function foo() }, this: {...}
}
複製代碼
(b):當js調用foo(22)時,進入到foo()函數的執行上下文,首先進行該上下文的建立階段。
ExecutionContextFoo = {
scopeChain: {...},
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c() a: undefined, b: undefined }, this: {...}
}
複製代碼
當執行階段運行完後,ExecutionContextFoo以下。
fooExecutionContext = {
scopeChain: { ... },
variableObject: {
arguments: {
0: 22,
length: 1
},
i: 22,
c: pointer to function c() a: 'hello', b: pointer to function privateB() }, this: { ... }
}
複製代碼
理清了JS中的執行上下文,就很容易明白變量提高具體是怎麼回事了。在代碼執行前,執行上下文已經給對應的聲明賦值,只不過變量是賦值爲undefined
,函數賦值爲對應的引用,然後在執行階段再將對應值賦值給變量。
首先看下面幾個代碼片斷,分別輸出是什麼?
Question 1:
function foo(){
function bar() {
return 3;
}
return bar();
function bar() {
return 8;
}
}
alert(foo());
複製代碼
Question 2:
function foo(){
var bar = function() {
return 3;
};
return bar();
var bar = function() {
return 8;
};
}
alert(foo());
複製代碼
Question 3:
alert(foo());
function foo(){
var bar = function() {
return 3;
};
return bar();
var bar = function() {
return 8;
};
}
複製代碼
Question 4:
function foo(){
return bar();
var bar = function() {
return 3;
};
var bar = function() {
return 8;
};
}
alert(foo());
複製代碼
上面4個代碼片斷分別輸出 8
,3
,3
,[Type Error: bar is not a function]
。
function name([param,[, param,[..., param]]]) { [statements] }
函數聲明以關鍵字function
開頭定義函數,同時有肯定的函數名。如最簡單的栗子:
function bar() {
return 3;
}
複製代碼
經過函數執行上下文,函數聲明會產生hoisted,即函數聲明會提高到代碼最上面。
因此在Question 1中,foo.VO中 bar:pointer to the function bar()
,由於有聲明瞭兩次bar()
函數,因此後面的定義覆蓋前面的定義。
var myFunction = function [name]([param1[, param2[, ..., paramN]]]) { statements };
函數表達式中,函數名字能夠省略,簡單栗子以下:
//anonymous function expression
var a = function() {
return 3;
}
//named function expression
var a = function bar() {
return 3;
}
//self invoking function expression
(function sayHello() {
alert("hello!");
})();
複製代碼
以上三種都是函數表達式,最後一種是當即執行函數。函數表達式不會提高到代碼最上面,如Question 2中,在函數執行上下文的建立階段中,foo.VO 中 bar : undefined
,在執行階段才進行賦值。
在回頭看看Question 4:
function foo(){
return bar(); // 執行階段返回調用bar(),但建立階段bar被賦值爲 undefined,因此報Type Error。
var bar = function() {
return 3;
};
var bar = function() {
return 8;
};
}
alert(foo());
複製代碼
參考
What is the Execution Context & Stack in JavaScript?