在之前的介紹中,我們已經(jīng)知道 Javascript 沒(méi)有塊級(jí)作用,只有函數(shù)級(jí)作用域。
function test() { // a scope for(var i = 0; i < 10; i++) { // not a scope // count } console.log(i); // 10 }
Javascript 中也沒(méi)有顯示的命名空間,這就意味著一切都定義在全局作用域中。每一次引用一個(gè)變量時(shí),Javascript 會(huì)往上遍歷整個(gè)全局作用域直到找到該變量。如果遍歷完整個(gè)全局作用域仍然沒(méi)有找到該變量,則拋出一個(gè) ReferenceError 錯(cuò)誤。
隱式全局變量// script A foo = "42"; // script B var foo = "42"
上面的兩個(gè)例子產(chǎn)生不一樣的效果。第一個(gè)將在全局作用域中定義變量 foo,而第二個(gè)則在當(dāng)前作用域定義變量 foo。
我們一定要注意,如果不使用關(guān)鍵字 var 將會(huì)帶來(lái)意想不到的影響。
// global scope var foo = 42; function test() { // local scope foo = 21; } test(); foo; // 21
由于在函數(shù) test 內(nèi)沒(méi)用 var 來(lái)定義變量 foo,所以將覆蓋函數(shù)外部的全局變量 foo。盡管看上去不是什么大問(wèn)題,但是如果有成千上萬(wàn)行代碼時(shí),這將是個(gè)難以追蹤的 bug。
// global scope var items = [/* some list */]; for(var i = 0; i < 10; i++) { subLoop(); } function subLoop() { // scope of subLoop for(i = 0; i < 10; i++) { // missing var statement // do amazing stuff! } }
上例中,外部的循環(huán)將會(huì)在執(zhí)行第一次的時(shí)候就停止,這是因?yàn)?subloop 函數(shù)內(nèi)部的變量 i 將會(huì)覆蓋外部的全局變量 i。我們只需要在函數(shù)內(nèi)部加上一個(gè) var 就可以避免這個(gè)錯(cuò)誤,所以我們?cè)诙x變量時(shí)一定不要忘記加上關(guān)鍵字 var。除非我們確實(shí)希望對(duì)外部的全局變量造成影響。
局部變量Javascript 中局部變量只可以通過(guò)兩個(gè)方式產(chǎn)生,一是通過(guò)關(guān)鍵字 var 來(lái)聲明,一是作為函數(shù)的形參。
// global scope var foo = 1; var bar = 2; var i = 2; function test(i) { // local scope of the function test i = 5; var foo = 3; bar = 4; } test(10);
此時(shí),函數(shù) test 內(nèi)部的變量 i 和 foo 是局部變量,而 bar 則會(huì)覆蓋外部的全局變量 bar。
提升(Hoisting)Javascript 將會(huì)提升變量聲明,這就意味著 var 表達(dá)式和函數(shù)聲明都將被提升到作用域的頂部。
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)行之前, var 表達(dá)式和函數(shù) test 的聲明都將提升至頂部,因此程序?qū)⒄_\(yùn)行并不會(huì)報(bào)錯(cuò)。
// var statements got moved here var bar, someValue; // default to "undefined" // the function declaration got moved up too function test(data) { var goo, i, e; // missing block scope moves these here if (false) { goo = 1; } else { goo = 2; } for(i = 0; i < 100; i++) { e = data[i]; } } bar(); // fails with a TypeError since bar is still "undefined" someValue = 42; // assignments are not affected by hoisting bar = function() {}; test();
由于 Javascript 沒(méi)有塊級(jí)作用域,這不僅將提升 var 表達(dá)式,同時(shí)也會(huì)使得 if 結(jié)構(gòu)變得不夠直觀。
在上例中,盡管看上去 if 在對(duì)全局變量 goo 進(jìn)行操作,實(shí)際上,由于變量 goo 被提升,所以修改的是局部變量。
如果沒(méi)有對(duì)提升規(guī)則有所了解,你可能會(huì)認(rèn)為下面的代碼將會(huì)拋出 ReferenceError 錯(cuò)誤。
// check whether SomeImportantThing has been initialized if (!SomeImportantThing) { var SomeImportantThing = {}; }
當(dāng)然上面的代碼是沒(méi)有錯(cuò)誤的,因?yàn)樵诖a在運(yùn)行前,var 表達(dá)式已經(jīng)被提升到頂部。
var SomeImportantThing; // other code might initialize SomeImportantThing here, or not // make sure it"s there if (!SomeImportantThing) { SomeImportantThing = {}; }
名稱解析順序這里要推薦下 @nightire 凡哥的博文 《理解 JavaScript(二)》,里面對(duì)提升的講解非常透徹。
當(dāng)嘗試在一個(gè)函數(shù)作用域內(nèi)訪問(wèn)一個(gè) foo 變量時(shí),Javascript 將會(huì)按照下面的順序查找:
當(dāng)前作用域內(nèi)是否有 var foo 的定義。
函數(shù)形參中是否有 foo 變量。
函數(shù)自身的名稱是否為 foo。
跳到外層定義域,再?gòu)牡谝徊块_(kāi)始查找起。
命名空間一個(gè)最常見(jiàn)的問(wèn)題就是命名沖突,這是因?yàn)?Javascript 只有一個(gè)全局作用域所帶來(lái)的。但這個(gè)問(wèn)題可以通過(guò)匿名的外部函數(shù)解決。
(function() { // a self contained "namespace" window.foo = function() { // an exposed closure }; })(); // execute the function immediately
上例中的匿名函數(shù)被認(rèn)為是表達(dá)式,所以它們會(huì)被執(zhí)行。
( // evaluate the function inside the parentheses function() {} ) // and return the function object () // call the result of the evaluation
當(dāng)然我們也可以用其他方式來(lái)調(diào)用函數(shù)表達(dá)式,不同的結(jié)構(gòu),但是同樣的效果。
// A few other styles for directly invoking the !function(){}() +function(){}() (function(){}()); // and so on...總結(jié)
建議大家使用匿名的外部函數(shù)來(lái)將代碼封裝到空間內(nèi),這樣不僅可以解決命名空間的沖突,同時(shí)也有利于程序的模塊化。
此外,使用全局變量不是一個(gè)好習(xí)慣,這將帶來(lái)高成本的維護(hù)代價(jià)而且容易產(chǎn)生錯(cuò)誤。
http://bonsaiden.github.io/JavaScript-Garden/#function.scopes
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/78064.html
摘要:全局變量函數(shù)內(nèi)的局部作用域和是函數(shù)內(nèi)的局部變量,而對(duì)的賦值將會(huì)覆蓋全局作用域內(nèi)的同名變量。命名空間只有一個(gè)全局作用域?qū)е碌某R?jiàn)錯(cuò)誤是命名沖突。另外兩種方式結(jié)論推薦使用匿名包裝器譯者注也就是自執(zhí)行的匿名函數(shù)來(lái)創(chuàng)建命名空間。 盡管 JavaScript 支持一對(duì)花括號(hào)創(chuàng)建的代碼段,但是并不支持塊級(jí)作用域; 而僅僅支持 函數(shù)作用域。 function test() { // 一個(gè)作用域 ...
摘要:不同的是函數(shù)體并不會(huì)再被提升至函數(shù)作用域頭部,而僅會(huì)被提升到塊級(jí)作用域頭部避免全局變量在計(jì)算機(jī)編程中,全局變量指的是在所有作用域中都能訪問(wèn)的變量。 ES6 變量作用域與提升:變量的生命周期詳解從屬于筆者的現(xiàn)代 JavaScript 開(kāi)發(fā):語(yǔ)法基礎(chǔ)與實(shí)踐技巧系列文章。本文詳細(xì)討論了 JavaScript 中作用域、執(zhí)行上下文、不同作用域下變量提升與函數(shù)提升的表現(xiàn)、頂層對(duì)象以及如何避免創(chuàng)建...
摘要:之前一篇文章我們?cè)敿?xì)說(shuō)明了變量對(duì)象,而這里,我們將詳細(xì)說(shuō)明作用域鏈。而的作用域鏈,則同時(shí)包含了這三個(gè)變量對(duì)象,所以的執(zhí)行上下文可如下表示。下圖展示了閉包的作用域鏈。其中為當(dāng)前的函數(shù)調(diào)用棧,為當(dāng)前正在被執(zhí)行的函數(shù)的作用域鏈,為當(dāng)前的局部變量。 showImg(https://segmentfault.com/img/remote/1460000008329355);初學(xué)JavaScrip...
摘要:函數(shù)表達(dá)式對(duì)于函數(shù)聲明,函數(shù)的名稱是必須的,而對(duì)于函數(shù)表達(dá)式而言則是可選的,因此,就出現(xiàn)了匿名函數(shù)表達(dá)式和命名函數(shù)表達(dá)式。這是因?yàn)閷?duì)命名函數(shù)處理的機(jī)制,函數(shù)的名稱永遠(yuǎn)在函數(shù)內(nèi)部的作用域中有效。 function 是 Javascript 中的第一類對(duì)象,這就意味著函數(shù)可以像其他值一樣被傳遞。一個(gè)最常見(jiàn)的用法就是將一個(gè)匿名函數(shù)作為回調(diào)函數(shù)傳遞到另外一個(gè)異步函數(shù)中。 函數(shù)聲明 func...
摘要:而閉包的神奇之處正是可以阻止事情的發(fā)生。拜所聲明的位置所賜,它擁有涵蓋內(nèi)部作用域的閉包,使得該作用域能夠一直存活,以供在之后任何時(shí)間進(jìn)行引用。依然持有對(duì)該作用域的引用,而這個(gè)引用就叫閉包。 引子 先看一個(gè)問(wèn)題,下面兩個(gè)代碼片段會(huì)輸出什么? // Snippet 1 a = 2; var a; console.log(a); // Snippet 2 console.log(a); v...
閱讀 2059·2021-11-23 10:03
閱讀 4763·2021-11-22 09:34
閱讀 2584·2021-10-08 10:05
閱讀 2309·2019-08-30 15:53
閱讀 1756·2019-08-30 13:56
閱讀 1219·2019-08-29 16:52
閱讀 1197·2019-08-26 13:31
閱讀 3405·2019-08-26 11:45