今天我們來談談 JavaScript
的 作用域(javascript scope) ,這是老生常談的話題晒奕,這里我們會從 作用域 開始妄帘,會延伸到 預解析規(guī)則(預編譯) 、 表達式 兄裂、 變量提升 句旱、 函數(shù)提升 、 匿名函數(shù)表達式 晰奖、 具名函數(shù)表達式 等谈撒,徹底搞明白作用域這些事 ??
詳情,可查閱我的博客 lishaoy.net
變量提升和函數(shù)提升
在開始闡述之前匾南,我們來看一段代碼啃匿,看看結果是什么?
alert(a);
function a(){ alter(2); }
alert(a);
var a = 1
alert(a);
var a = 3;
alert(a);
function a(){ alter(4); }
alert(a);
a();
這里先揭曉答案:
- 第一個
alert(a)
彈出function a(){ alter(4); }
函數(shù)體- 第二個
alter(a)
彈出function a(){ alter(4); }
函數(shù)體- 第三個
alter(a)
彈出 1- 第四個
alter(a)
彈出 3- 第五個
alter(a)
彈出 3- 最后一行報錯
a is not a function
下面來分析一下這段代碼:
其實在 javascript
開始執(zhí)行代碼之前蛆楞,有一個 預解析(預編譯) 的過程溯乒,這個過程會產生 變量提升 和 函數(shù)提升 ,其實整個執(zhí)行過程可以分為兩部分豹爹,方便理解:
-
預解析
這個過程裆悄,會把 關鍵字var
、function
臂聋、 參數(shù) 提取出來
上面這段代碼 預解析 的過程是:
// 第1行光稼,沒有關鍵字 , 不解析
// 第2行孩等,遇到 function 關鍵字艾君,解析到全局的頭部
a = function a(){ alter(2); }
// 第3行,沒有關鍵字 肄方, 不解析
// 第4行冰垄,遇到關鍵字 var , 解析到全局的頭部
a = undefined
// 第5行扒秸,沒有關鍵字 播演, 不解析
// 第6行,遇到關鍵字 var 伴奥, 解析到全局的頭部
a = undefined
// 第8行写烤,遇到 function 關鍵字,解析到全局的頭部
a = function a(){ alter(4); }
// 第9行拾徙,沒有關鍵字 洲炊, 不解析
// 第10行,a() 函數(shù)調用
此時這里有4個同名變量 a ,依循規(guī)則是:function
優(yōu)先與 var
, 同名的后面覆蓋前面的
因此暂衡,a = function a(){ alter(2); }
替換掉下面的2個 询微,a = undefined
a = function a(){ alter(4); }
又替換掉 ,最終只剩下 a = function a(){ alter(2); }
a = function a(){ alter(4); }
預解析(預編譯) 后的代碼樣子是這樣的
var a = function a(){ alter(4); }
alert(a);
alert(a);
a = 1
alert(a);
a = 3;
alert(a);
alert(a);
a();
- 執(zhí)行代碼,就是執(zhí)行的這段代碼狂巢,依次從上到下執(zhí)行撑毛,最后的
a()
函數(shù)調用,這時的a
已被 表達式 賦值成 3 唧领,而報錯a is not a function
全局作用域和局部作用域
再看這段代碼
var a = 1;
function fn1(){
alert(a);
var a = 2;
}
fn1();
alert(a);
這里先揭曉答案:
- 第一個
alert(a)
彈出undefined
- 第二個
alert(a)
彈出 1
JavaScript
的作用域只用兩種藻雌,一個是全局的,一個是函數(shù)的斩个,也稱為 全局作用域 和 局部作用域 胯杭;局部作用域 可以訪問 全局作用域 。但是 全局作用域 不能訪問 局部作用域
同樣用 預解析(預編譯) 的方法來分析這段代碼
- 預解析(預編譯) 全局作用域
// 第1行受啥,遇到 var 關鍵字做个,解析到全局的頭部
a = undefined
// 第2行,遇到 function 關鍵字滚局,解析到全局的頭部
fn1 = function fn1(){
alert(a);
var a = 2;
}
// 第3行居暖,沒有遇到關鍵字,不解析
// 第4行藤肢,沒有遇到關鍵字膝但,不解析
- 開始執(zhí)行代碼
第1行,遇到表達式 a = 1
, a 被賦值成 1 </br>
第6行谤草,遇到函數(shù)調用 fn1()
,開始 預解析(預編譯) 局部
- 預解析(預編譯) 局部作用域
// 第3行,沒有遇到關鍵字莺奸,不解析
// 第4行丑孩,遇到 var 關鍵字,解析到局部
a = undefined
- 開始執(zhí)行 局部 代碼
第3行灭贷,彈出 undefined
第4行温学,遇到表達式,把局部 a 改成 2
- 局部執(zhí)行完成甚疟,繼續(xù)執(zhí)行全局
第7行仗岖,彈出 1 ,因為全局和局部是兩個獨立的作用域
作用域鏈
如果览妖,把上面??代碼轧拄,稍作修改
var a = 1;
function fn1(){
alert(a);
a = 2;
}
fn1();
alert(a);
去掉了 function
里的 var
,結果就會不一樣
這次讽膏,輸出的是:
- 第一個
alert
彈出 1 - 第二個
alert
彈出 2
因為在解析局部是沒有發(fā)現(xiàn)var a
檩电,如是在執(zhí)行時,就會去全局查找,找到了全局的a = 1
俐末,所以 第一個alert
彈出 1 料按,而不是undefined
,這個就是 作用域連
匿名函數(shù)表達式、具名函數(shù)表達式
在來看看這段代碼??
var a = 3;
function fn() {
foo();
function foo() {
console.log(1);
}
foo();
var foo = function() {
console.log(2);
};
foo();
var bar = function foo() {
if(a > 3) return;
console.log(++a);
foo();
};
foo();
bar();
}
fn();
先揭曉答案:
- 第1個
foo()
輸出的是 1- 第2個
foo()
輸出的是 1- 第3個
foo()
輸出的是 2- 第4個
foo()
輸出的是 2- 最后的
bar()
輸出的是 4
以上代碼包含了 函數(shù)聲明 卓箫、 匿名函數(shù)表達式 载矿、 具名函數(shù)表達式 ,匿名函數(shù)表達式 烹卒、 具名函數(shù)表達式 是把函數(shù)體賦值給一個變量闷盔,因此擁有和變量相同的特性 變量提升 ,而 具名函數(shù)表達式 的函數(shù)名只能在函數(shù)內部使用甫题。
了解了這些馁筐,再來分析段代碼
- 全局預解析
a = undefined
fn = function fn(){
...
}
執(zhí)行代碼
第1行,遇到表達式,把 a 的值改變成3 </br>
最后行坠非,遇到函數(shù)調用敏沉,重新 預解析 局部局部預解析
// 第4行,遇到 function 關鍵字炎码,解析到局部的頭部
foo = function(){
console.log(1);
}
// 第8行盟迟,遇到 var 關鍵字,解析到局部的頭部
foo = undefined
// 第12行潦闲,遇到 var 關鍵字攒菠,解析到局部的頭部
bar = undefined
由于有兩個同名變量 foo
,遵循 function
優(yōu)先 var
因此歉闰, 被干掉foo = undefined
局部預解析 完之后的代碼應該是這個樣子??
var a = 3
function fn() {
var foo = function foo() {
console.log(1);
}
var bar;
foo();
foo();
foo = function foo() {
console.log(2);
};
foo();
bar = function foo() {
if(a > 3) return;
console.log(++a);
foo();
};
foo();
bar();
}
fn();
-
執(zhí)行局部代碼 </br>
第1個foo()
輸出的是 1
第2個foo()
輸出的是 1
第3個foo()
輸出的是 2
第4個foo()
輸出的是 2 辖众,注意這個foo()
輸出的是上面foo = function foo() {console.log(2);}
的內容,因為 具名函數(shù)表達式 的函數(shù)名只能在函數(shù)內部使用和敬,在外部無法訪問凹炸。
最后的bar()
輸出的是 4 ,這里才是輸出function foo() {if(a > 3) return;console.log(++a);foo();}
里的內容昼弟,而且啤它,這個函數(shù)體內也有自身的調用,結果a
變量 +1 舱痘,說明可以調用变骡,其實,可以用bar.name
輸出的就是foo
所以芭逝,注意:
bar = function foo()
, 不要用這種寫法- 不推薦使用 匿名函數(shù)表達式 塌碌,有以下 ?? 幾個缺點
- 在追蹤棧中沒函數(shù)名,調試困難
- 如果需要引用自身铝耻,只能用非標準的
arguments.callee
(ES5嚴格模式禁用)