儘管 JavaScript 支持一對花括號建立的代碼段,可是並不支持塊級做用域;
而僅僅支持 函數做用域。javascript
function test() { // 一個做用域 for(var i = 0; i < 10; i++) { // 不是一個做用域 // count } console.log(i); // 10 }
注意: 若是不是在賦值語句中,而是在 return 表達式或者函數參數中,
{...}
將會做爲代碼段解析,
而不是做爲對象的字面語法解析。若是考慮到 自動分號插入,這可能會致使一些不易察覺的錯誤。java
譯者注:若是 return
對象的左括號和 return
不在一行上就會出錯。ajax
// 譯者注:下面輸出 undefined function add(a, b) { return a + b; } console.log(add(1, 2));
JavaScript 中沒有顯式的命名空間定義,這就意味着全部對象都定義在一個全局共享的命名空間下面。數組
每次引用一個變量,JavaScript 會向上遍歷整個做用域直到找到這個變量爲止。
若是到達全局做用域可是這個變量仍未找到,則會拋出 ReferenceError
異常。閉包
// 腳本 A foo = '42'; // 腳本 B var foo = '42'
上面兩段腳本效果不一樣。腳本 A 在全局做用域內定義了變量 foo
,而腳本 B 在當前做用域內定義變量 foo
。模塊化
再次強調,上面的效果徹底不一樣,不使用 var
聲明變量將會致使隱式的全局變量產生。函數
// 全局做用域 var foo = 42; function test() { // 局部做用域 foo = 21; } test(); foo; // 21
在函數 test
內不使用 var
關鍵字聲明 foo
變量將會覆蓋外部的同名變量。
起初這看起來並非大問題,可是當有成千上萬行代碼時,不使用 var
聲明變量將會帶來難以跟蹤的 BUG。oop
// 全局做用域 var items = [/* 數組 */]; for(var i = 0; i < 10; i++) { subLoop(); } function subLoop() { // subLoop 函數做用域 for(i = 0; i < 10; i++) { // 沒有使用 var 聲明變量 // 幹活 } }
外部循環在第一次調用 subLoop
以後就會終止,由於 subLoop
覆蓋了全局變量 i
。
在第二個 for
循環中使用 var
聲明變量能夠避免這種錯誤。
聲明變量時絕對不要遺漏 var
關鍵字,除非這就是指望的影響外部做用域的行爲。網站
JavaScript 中局部變量只可能經過兩種方式聲明,一個是做爲函數參數,另外一個是經過 var
關鍵字聲明。ui
// 全局變量 var foo = 1; var bar = 2; var i = 2; function test(i) { // 函數 test 內的局部做用域 i = 5; var foo = 3; bar = 4; } test(10);
foo
和 i
是函數 test
內的局部變量,而對 bar
的賦值將會覆蓋全局做用域內的同名變量。
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' // 函數聲明也會提高 function test(data) { var goo, i, e; // 沒有塊級做用域,這些變量被移動到函數頂部 if (false) { goo = 1; } else { goo = 2; } for(i = 0; i < 100; i++) { e = data[i]; } } bar(); // 出錯:TypeError,由於 bar 依然是 'undefined' someValue = 42; // 賦值語句不會被提高規則(hoisting)影響 bar = function() {}; test();
沒有塊級做用域不只致使 var
表達式被從循環內移到外部,並且使一些 if
表達式更難看懂。
在原來代碼中,if
表達式看起來修改了所有變量 goo
,實際上在提高規則被應用後,倒是在修改局部變量。
若是沒有提高規則(hoisting)的知識,下面的代碼看起來會拋出異常 ReferenceError
。
// 檢查 SomeImportantThing 是否已經被初始化 if (!SomeImportantThing) { var SomeImportantThing = {}; }
實際上,上面的代碼正常運行,由於 var
表達式會被提高到全局做用域的頂部。
var SomeImportantThing; // 其它一些代碼,可能會初始化 SomeImportantThing,也可能不會 // 檢查是否已經被初始化 if (!SomeImportantThing) { SomeImportantThing = {}; }
譯者注:在 Nettuts+ 網站有一篇介紹 hoisting 的文章,其中的代碼頗有啓發性。
// 譯者注:來自 Nettuts+ 的一段代碼,生動的闡述了 JavaScript 中變量聲明提高規則 var myvar = 'my value'; (function() { alert(myvar); // undefined var myvar = 'local value'; })();
JavaScript 中的全部做用域,包括全局做用域,都有一個特別的名稱 this
指向當前對象。
函數做用域內也有默認的變量 arguments
,其中包含了傳遞到函數中的參數。
好比,當訪問函數內的 foo
變量時,JavaScript 會按照下面順序查找:
var foo
的定義。foo
名稱的。foo
。注意: 自定義
arguments
參數將會阻止原生的arguments
對象的建立。
只有一個全局做用域致使的常見錯誤是命名衝突。在 JavaScript中,這能夠經過 匿名包裝器 輕鬆解決。
(function() { // 函數建立一個命名空間 window.foo = function() { // 對外公開的函數,建立了閉包 }; })(); // 當即執行此匿名函數
匿名函數被認爲是 表達式;所以爲了可調用性,它們首先會被執行。
( // 小括號內的函數首先被執行 function() {} ) // 而且返回函數對象 () // 調用上面的執行結果,也就是函數對象
有一些其餘的調用函數表達式的方法,好比下面的兩種方式語法不一樣,可是效果如出一轍。
// 另外兩種方式 +function(){}(); (function(){}());
推薦使用匿名包裝器(譯者注:也就是自執行的匿名函數)來建立命名空間。這樣不只能夠防止命名衝突,
並且有利於程序的模塊化。
另外,使用全局變量被認爲是很差的習慣。這樣的代碼傾向於產生錯誤和帶來高的維護成本。