前言
今天發現了兩個關於函式作用域的神奇例子,這裡和大家分享分享:
第一個例子
var a = 1;
function foo() {
if (!a) {
var a = 2;
}
alert(a);
};
foo();
上面這段程式碼在執行時會產生什麼結果?
我們來分析一下:
1.建立了全域性變數 a,定義其值為 1
2.建立了函式 foo
3.在 foo 的函式體內,if 語句將不會執行,因為 !a 會將變數 a 轉變成布林的假值,也就是 false
4.跳過條件分支,alert 變數 a,最終的結果應該是輸出 1
看起來無懈可擊的分析,但是實際上,結果錯誤。答案竟然是 2!為什麼?
什麼叫申明?
是指你聲稱某樣東西的存在,比如一個變數或一個函式;但你沒有說明這樣東西到底是什麼,僅僅是告訴直譯器這樣東西存在而已;
什麼叫定義?
是指你指明瞭某樣東西的具體實現,比如一個變數的值是多少,一個函式的函式體是什麼,確切的表達了這樣東西的意義。
所以上面的程式碼實際上可以寫成這樣:
var a;
a = 1;
function foo() {
var a; // 關鍵在這裡
if (!a) {
a = 2;
}
alert(a); // 此時的 a 並非函式體外的那個全域性變數
}
foo();
然後又有人會問,不是有個if嗎?if不成立哪就不會為a賦值為2。
因為 JavaScript 沒有塊級作用域(Block Scoping),只有函式作用域(Function Scoping),所以說不是看見一對花括號 {} 就代表產生了新的作用域,和 C 不一樣!
當解析器讀到 if 語句的時候,它發現此處有一個變數宣告和賦值,於是解析器會將其宣告提升至當前作用域的頂部(這是預設行為,並且無法更改),這個行為就叫做 Hoisting。
怎樣能夠alert出那個a=1?
let a;
a = 1;
function foo() {
let a; // 關鍵在這裡
if (!a) {
a = 2;
}
alert(a); // 此時的 a 並非函式體外的那個全域性變數
}
foo();
es6的語法,javascript是有塊級作用域的。
還可以通過閉包的方式實現:
var a = 1;
function foo() {
if (!a) {
(function() {
var a = 2;
}());
};
alert(a);
};
foo();
第二個例子
var a = 1;
function test() {
foo();
var foo = function() {
alert(a);
}
}
test();
這個執行的結果是什麼?初略一看,alert(1),但是實際上報錯。
Uncaught TypeError: foo is not a function。
為什麼會這樣?
提升的僅僅是變數名 foo,至於它的定義依然停留在原處。因此在執行 foo() 之前,作用域只知道 foo 的命名,不知道它到底是什麼,所以執行會報錯(通常會是:undefined is not a function)。這叫做函式表示式(Function Expression),函式表示式只有命名會被提升,定義的函式體則不會。
var a = 1;
function test() {
var foo;
foo(); // 這個時候函式foo,只宣告,未賦值。
foo = function() {
alert(a);
}
}
test();
怎麼改?
var a = 1;
function test() {
var foo = function() {
alert(a);
}
foo();
}
test(); // 1
這個例子也展示了函式宣告與函式表示式的差別,函式申明會放到作用域的頂部,函式表示式則不會。
最後引用很多書中的一句話:“請始終保持作用域內所有變數的宣告放置在作用域的頂部”,相信現在的你對這句話應該有一個認識了。