JavaScript 經過函數管理做用域。在函數內部聲明的變量只在這個函數內部,函數外面不可用。另外一方面,全局變量就是在任何函數外面聲明的或是未聲明直接簡單使用的。閉包
「未聲明直接簡單使用」,指的是不用 var
關鍵字來聲明變量。這個咱們已經很是清楚,避免形成隱式產生全局變量的方法就是聲明變量儘可能用 var
關鍵字。函數
可你覺得用了 var
就 ok 了?來看看這個陷進:測試
1spa 2debug 3code 4遞歸 |
function foo() {索引 var a = b = 0;ip // body...作用域 } |
也許你指望獲得的是兩個局部變量,但 b
倒是貨真價實的全局變量。why? Because 賦值運算是自右往左的 ,因此這至關於:
1 2 3 4 |
function foo() { var a = (b = 0); // body... } |
因此 b
是全局變量。
解決:變量聲明,最好一個個來,別搞批發~_~;
先來看陷阱:
1 2 3 4 5 6 7 8 9 |
myName = "global";
function foo() { alert(myName); var myName = "local"; alert(myName); }
foo(); |
乍看上去,咱們預計指望兩次 alert
的結果分別爲 「global」 與 「local」,但真實的結果是 「undefined」 與 「local」。why? Because 變量在同一做用域(同一函數)中,聲明都是被提至做用域頂部先進行解析的。
因此以上代碼片斷的執行行爲可能就像這樣:
1 2 3 4 5 6 |
function foo() { var myName; alert(myName); // "undefined" myName = "local"; alert(myName); // "local" } |
用另外一個陷阱來測試下你是否真的理解了預解析:
1 2 3 4 5 |
if (!("a" in window)) { var a = 1; }
alert(a); |
a
變量的聲明被提早到了代碼頂端,此時還未賦值。接下來進入 if
語句,判斷條件中 "a" in window
已成立(a
已被聲明爲全局變量),因此判斷語句計算結果爲 false
,直接就跳出 if
語句了,因此 a
的值爲 undefined
。
1 2 3 4 5 6 7 8 |
var a; // "undefined" console.log("a" in window); // true
if (!("a" in window)) { var a = 1; // 不執行 }
alert(a); // "undefined" |
解決:變量聲明,最好手動置於做用域頂部,對於沒法當下賦值的變量,可採起先聲明後賦值的手法。
函數聲明也是被提早至做用域頂部,先於任何表達式和語句被解析和求值的
1 2 3 4 5 |
alert(typeof foo); // "function"
function foo() { // body... } |
能夠對比一下:
1 2 3 4 5 |
alert(typeof foo); // "undefined"
var foo = function () { // body... }; |
明白了這個道理的你,是否還會掉入如下的陷阱呢?
1 2 3 4 5 6 7 8 9 10 11 |
function test() { alert("1"); }
test();
function test() { alert("2"); }
test(); |
運行以上代碼片斷,看到的兩次彈窗顯示的都是 「2」,爲何不是分別爲 「1」 和 「2」 呢?很簡單,test
的聲明先於 test()
被解析,因爲後者覆蓋前者,因此兩次執行的結果都是 「2」。
解決:多數狀況下,我用函數表達式來代替函數聲明,特別是在一些語句塊中。
先看命名函數表達式,理所固然,就是它得有名字,例如:
1 2 3 |
var bar = function foo() { // body... }; |
要注意的是:函數名只對其函數內部可見。如如下陷阱:
1 2 3 4 5 |
var bar = function foo() { foo(); // 正常運行 };
foo(); // 出錯:ReferenceError |
解決:儘可能少用命名函數表達式(除了一些遞歸以及 debug 的用途),切勿將函數名使用於外部。
對於函數表達式,能夠經過後面加上 ()
自執行,並且可在括號中傳遞參數,而函數聲明不能夠。陷阱:
1 2 3 4 5 |
// (1) 這只是一個分組操做符,不是函數調用! // 因此這裏函數未被執行,依舊是個聲明 function foo(x) { alert(x); }(1); |
如下代碼片斷分別執行都彈窗顯示 「1」,由於在 (1)
以前,都爲函數表達式,因此這裏的 ()
非分組操做符,而爲運算符,表示調用執行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
// 標準的匿名函數表達式 var bar = function foo(x) { alert(x); }(1);
// 前面的 () 將 function 聲明轉化爲了表達式 (function foo(x) { alert(x); })(1);
// 整個 () 內爲表達式 (function foo(x) { alert(x); }(1));
// new 表達式 new function foo(x) { alert(x); }(1);
// &&, ||, !, +, -, ~ 等操做符(還有逗號),在函數表達式和函數聲明上消除歧義 // 因此一旦解析器知道其中一個已是表達式了,其它的也都默認爲表達式了 true && function foo(x) { alert(x); }(1); |
解決:這個陷阱的關鍵在於,弄清楚形形色色函數表達式的實質。
如下演示的是一個常見的陷阱:
1 2 3 4 5 6 7 8 |
var links = document.getElementsByTagName("ul")[0].getElementsByTagName("a");
for (var i = 0, l = links.length; i < l; i++) { links[i].onclick = function (e) { e.preventDefault(); alert("You click link #" + i); } } |
咱們預期當點擊第 i
個連接時,獲得此序列索引 i
的值,可實際不管點擊哪一個連接,獲得的都是 i
在循環後的最終結果:」5」。
解釋一下緣由:當 alert
被調用時,for
循環內的匿名函數表達式,保持了對外部變量 i
的引用(閉包),此時循環已結束,i
的值被修改成 「5」。
解決:爲了獲得想要的結果,須要在每次循環中建立變量 i
的拷貝。如下演示正確的作法:
1 2 3 4 5 6 7 8 9 10 |
var links = document.getElementsByTagName("ul")[0].getElementsByTagName("a");
for (var i = 0, l = links.length; i < l; i++) { links[i].onclick = (function (index) { return function (e) { e.preventDefault(); alert("You click link #" + index); } })(i); } |
能夠看到,(function () { ... })()
的形式,就是上文提到的 函數的自執行 ,i
做爲參數傳給了 index
,alert
再次執行時,它就擁有了對 index
的引用,此時這個值是不會被循環改變的。固然,明白了其原理後,你也能夠這樣寫:
JavaScript
1 2 3 4 5 6 7 8 |
for (var i = 0, l = links.length; i < l; i++) { (function (index) { links[i].onclick = function (e) { e.preventDefault(); alert("You click link #" + index); } })(i); } |