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