盡管 JavaScript 支持一對花括號創(chuàng)建的代碼段,但是并不支持塊級作用域; 而僅僅支持函數(shù)作用域。
function test() { // 一個作用域
for(var i = 0; i < 10; i++) { // 不是一個作用域
// count
}
console.log(i); // 10
}
注意: 如果不是在賦值語句中,而是在 return 表達式或者函數(shù)參數(shù)中,
{...}
將會作為代碼段解析, 而不是作為對象的字面語法解析。如果考慮到自動分號插入,這可能會導致一些不易察覺的錯誤。
譯者注:如果 return
對象的左括號和 return
不在一行上就會出錯。
// 譯者注:下面輸出 undefined
function add(a, b) {
return
a + b;
}
console.log(add(1, 2));
JavaScript 中沒有顯式的命名空間定義,這就意味著所有對象都定義在一個全局共享的命名空間下面。
每次引用一個變量,JavaScript 會向上遍歷整個作用域直到找到這個變量為止。
如果到達全局作用域但是這個變量仍未找到,則會拋出 ReferenceError
異常。
// 腳本 A
foo = '42';
// 腳本 B
var foo = '42'
上面兩段腳本效果不同。腳本 A 在全局作用域內(nèi)定義了變量 foo
,而腳本 B 在當前作用域內(nèi)定義變量 foo
。
再次強調(diào),上面的效果完全不同,不使用 var
聲明變量將會導致隱式的全局變量產(chǎn)生。
// 全局作用域
var foo = 42;
function test() {
// 局部作用域
foo = 21;
}
test();
foo; // 21
在函數(shù) test
內(nèi)不使用 var
關鍵字聲明 foo
變量將會覆蓋外部的同名變量。
起初這看起來并不是大問題,但是當有成千上萬行代碼時,不使用 var
聲明變量將會帶來難以跟蹤的 BUG。
// 全局作用域
var items = [/* 數(shù)組 */];
for(var i = 0; i < 10; i++) {
subLoop();
}
function subLoop() {
// subLoop 函數(shù)作用域
for(i = 0; i < 10; i++) { // 沒有使用 var 聲明變量
// 干活
}
}
外部循環(huán)在第一次調(diào)用 subLoop
之后就會終止,因為 subLoop
覆蓋了全局變量 i
。
在第二個 for
循環(huán)中使用 var
聲明變量可以避免這種錯誤。
聲明變量時絕對不要遺漏 var
關鍵字,除非這就是期望的影響外部作用域的行為。
JavaScript 中局部變量只可能通過兩種方式聲明,一個是作為函數(shù)參數(shù),另一個是通過 var
關鍵字聲明。
// 全局變量
var foo = 1;
var bar = 2;
var i = 2;
function test(i) {
// 函數(shù) test 內(nèi)的局部作用域
i = 5;
var foo = 3;
bar = 4;
}
test(10);
foo
和 i
是函數(shù) test
內(nèi)的局部變量,而對 bar
的賦值將會覆蓋全局作用域內(nèi)的同名變量。
JavaScript 會提升變量聲明。這意味著 var
表達式和 function
聲明都將會被提升到當前作用域的頂部。
bar();
var bar = function() {};
var someValue = 42;
test();
function test(data) {
if (false) {
goo = 1;
} else {
var goo = 2;
}
for(var i = 0; i < 100; i++) {
var e = data[i];
}
}
上面代碼在運行之前將會被轉化。JavaScript 將會把 var
表達式和 function
聲明提升到當前作用域的頂部。
// var 表達式被移動到這里
var bar, someValue; // 缺省值是 'undefined'
// 函數(shù)聲明也會提升
function test(data) {
var goo, i, e; // 沒有塊級作用域,這些變量被移動到函數(shù)頂部
if (false) {
goo = 1;
} else {
goo = 2;
}
for(i = 0; i < 100; i++) {
e = data[i];
}
}
bar(); // 出錯:TypeError,因為 bar 依然是 'undefined'
someValue = 42; // 賦值語句不會被提升規(guī)則(hoisting)影響
bar = function() {};
test();
沒有塊級作用域不僅導致 var
表達式被從循環(huán)內(nèi)移到外部,而且使一些 if
表達式更難看懂。
在原來代碼中,if
表達式看起來修改了全局變量 goo
,實際上在提升規(guī)則被應用后,卻是在修改局部變量。
如果沒有提升規(guī)則(hoisting)的知識,下面的代碼看起來會拋出異常 ReferenceError
。
// 檢查 SomeImportantThing 是否已經(jīng)被初始化
if (!SomeImportantThing) {
var SomeImportantThing = {};
}
實際上,上面的代碼正常運行,因為 var
表達式會被提升到全局作用域的頂部。
var SomeImportantThing;
// 其它一些代碼,可能會初始化 SomeImportantThing,也可能不會
// 檢查是否已經(jīng)被初始化
if (!SomeImportantThing) {
SomeImportantThing = {};
}
譯者注:在 Nettuts+ 網(wǎng)站有一篇介紹 hoisting 的文章,其中的代碼很有啟發(fā)性。
// 譯者注:來自 Nettuts+ 的一段代碼,生動的闡述了 JavaScript 中變量聲明提升規(guī)則
var myvar = 'my value';
(function() {
alert(myvar); // undefined
var myvar = 'local value';
})();
JavaScript 中的所有作用域,包括全局作用域,都有一個特別的名稱this
指向當前對象。
函數(shù)作用域內(nèi)也有默認的變量arguments
,其中包含了傳遞到函數(shù)中的參數(shù)。
比如,當訪問函數(shù)內(nèi)的 foo
變量時,JavaScript 會按照下面順序查找:
var foo
的定義。foo
名稱的。foo
。注意: 自定義
arguments
參數(shù)將會阻止原生的arguments
對象的創(chuàng)建。
只有一個全局作用域?qū)е碌某R婂e誤是命名沖突。在 JavaScript中,這可以通過 匿名包裝器 輕松解決。
(function() {
// 函數(shù)創(chuàng)建一個命名空間
window.foo = function() {
// 對外公開的函數(shù),創(chuàng)建了閉包
};
})(); // 立即執(zhí)行此匿名函數(shù)
匿名函數(shù)被認為是表達式;因此為了可調(diào)用性,它們首先會被執(zhí)行。
( // 小括號內(nèi)的函數(shù)首先被執(zhí)行
function() {}
) // 并且返回函數(shù)對象
() // 調(diào)用上面的執(zhí)行結果,也就是函數(shù)對象
有一些其他的調(diào)用函數(shù)表達式的方法,比如下面的兩種方式語法不同,但是效果一模一樣。
// 另外兩種方式
+function(){}();
(function(){}());
推薦使用匿名包裝器(譯者注:也就是自執(zhí)行的匿名函數(shù))來創(chuàng)建命名空間。這樣不僅可以防止命名沖突, 而且有利于程序的模塊化。
另外,使用全局變量被認為是不好的習慣。這樣的代碼容易產(chǎn)生錯誤并且維護成本較高。