每一個函數都有自己的執行上下文EC(執行環境 execution context),並且每個執行上下文中都有它自己的變量對象VO(Variable object),用於存儲執行上下文中的變量 、函數聲明 、函數參數,這解釋了js如何找到我們定義的函數和變量。並且函數是js中唯一一個能創建出作用域的,注意:for,if()else()不能創建作用域。我們通過以下幾個例子說明為什么函數和變量的聲明會被前置,為什么匿名函數表達式的不可以在外面調用。
var a = 18; f1(); function f1(){ var b=9; console.log(a); //undefined console.log(b); //9 var a = '123'; }
因為在f1中,在此函數還未執行時變量a,b會被提前聲明,也就是說可以理解為下面的代碼
var a = 18; f1(); function f1(){ var b; var a; b = 9; console.log(a); console.log(b); a = '123'; }
如果想進一步理解內部的運行機制,可以用如下方法:
在此案例中,f1函數的作用域鏈有兩個對象,一個是全局變量對象,一個是f1變量對象。
VO (globalContext) = {
a: 18,
f1: <ref to function>
}
1.變量初始化階段
VO (f1 functionContext) = {
a: undefined,
b: undefined
}
2.代碼執行階段
VO (f1 functionContext) = {
a: undefined,
b: 9
}
再來兩個js經典題
function fn(a){ console.log(a); //function(){} var a = 2; function a(){}; console.log(a); //2 } fn(1);
我們知道,在運行fn之前,變量和function都會提前聲明,但是function會覆蓋變量(在不賦值的前提下),
通過以上案例,我們可以總結如下規律:
VO按照如下順序填充:
1. 函數參數 (若未傳入,初始化該參數值為undefined)
2. 函數聲明 (若發生命名沖突,會覆蓋)
3. 變量聲明 (初始化變量值為undefined,若發生命名沖突,會忽略。)
注意:變量初始化(被聲明)和變量根本不在一個變量對象里是有區別的,如果用console打印前者會顯示undefined,而后者會報一個"ReferenceError: gg is not defined"。
比如
第一個只有一個全局上下文
VO (globalContext) = {
fn: <ref to function>
}
因為沒有執行fn,所以里面的變量讀取不到,第二個通過聲明被前置,打印undefined.(只有var的變量才能被前置)
為了讓大家充分了解執行上下文,再來一個例子。
function test(a,b){ var c = 10; function d(){} var e = function _e(){}; (function x(){}); b = 20; } test(10);
第一階段——變量初始化階段如下 第二階段——代碼執行階段
提示:因為此函數中有形參b,所以在變量初始化階段會b:undefined,如果沒有形參b,會報錯 b is not defined。
最后來一個壓軸的
alert(a); //undefined
alert(b); //undefined
alert(x); //function x(){}
var x = 10;
alert(x); // 10
x = 20;
function x(){}
alert(x); // 20
if (true) {
var a = 1;
} else {
var b = true;
}
alert(a); //1
alert(b); //undefined
首先聲明一點,js是沒有塊級作用域的,所以if{}else{}里面的變量即使不執行,他們的聲明也會前置,所以第一個和第二個alert為undefined,對於第三個alert,因為先是x這個變量前置,然后x又變成了function,前面說過了,函數聲明如果發生沖突會覆蓋變量聲明(可以理解為function的優先級更高),所以第三個彈出function,第四個賦值為10,第五個x=20覆蓋x=10,第六個因為執行了if,所以給a賦值彈出1,最后一個還是undefined。