js作用域及作用域链概念理解及使用
之前寫過一篇JavaScript 閉包究竟是什么的文章理解閉包,覺得寫得很清晰,可以簡(jiǎn)單理解閉包產(chǎn)生原因,但看評(píng)論都在說了解了作用域鏈和活動(dòng)對(duì)象才能真正理解閉包,起初不以為然,后來在跟公司同事交流的時(shí)候發(fā)現(xiàn)作用域和執(zhí)行環(huán)境確實(shí)很重要,又很基礎(chǔ),對(duì)理解JavaScript閉包很有幫助,所以在寫一篇對(duì)作用域和執(zhí)行環(huán)境的理解。
作用域
作用域就是變量和函數(shù)的可訪問范圍,控制著變量和函數(shù)的可見性與生命周期,在JavaScript中變量的作用域有全局作用域和局部作用域。
單純的JavaScript作用域還是很好理解的,在一些類C編程語言中花括號(hào)內(nèi)的每一段代碼都有各自的作用域,而且變量在聲明它們的代碼段外是不可見的,稱之為塊級(jí)的作用域,JavaScript容易讓初學(xué)者誤會(huì)的地方也在于此,JavaScript并沒有塊及的作用域,只有函數(shù)級(jí)作用域:變量在聲明它們的函數(shù)體及其子函數(shù)內(nèi)是可見的。
變量沒有在函數(shù)內(nèi)聲明或者聲明的時(shí)候沒有帶var就是全局變量,擁有全局作用域,window對(duì)象的所有屬性擁有全局作用域;在代碼任何地方都可以訪問,函數(shù)內(nèi)部聲明并且以var修飾的變量就是局部變量,只能在函數(shù)體內(nèi)使用,函數(shù)的參數(shù)雖然沒有使用var但仍然是局部變量。
var a=3; //全局變量function fn(b){ //局部變量c=2; //全局變量var d=5; //局部變量function subFn(){var e=d; //父函數(shù)的局部變量對(duì)子函數(shù)可見for(var i=0;i<3;i++){console.write(i);}alert(i);//3, 在for循環(huán)內(nèi)聲明,循環(huán)外function內(nèi)仍然可見,沒有塊作用域 }}alert(c); //在function內(nèi)聲明但不帶var修飾,仍然是全局變量只要是理解了JavaScript沒有塊作用域,簡(jiǎn)單的JavaScript作用域很好理解,還有一點(diǎn)兒容易讓初學(xué)者迷惑的地方是JavaScript變量可函數(shù)的與解析或者聲明提前,好多種叫法但說的是一件事情,JavaScript雖然是解釋執(zhí)行,但也不是按部就班逐句解釋執(zhí)行的,在真正解釋執(zhí)行之前,JavaScript解釋器會(huì)預(yù)解析代碼,將變量、函數(shù)聲明部分提前解釋,這就意味著我們可以在function聲明語句之前調(diào)用function,這多數(shù)人習(xí)以為常,但是對(duì)于變量的與解析乍一看會(huì)很奇怪
console.log(a); //undefinedvar a=3;console.log(a); //3console.log(b); //Uncaught ReferenceError: b is not defined上面代碼在執(zhí)行前var a=3; 的聲明部分就已經(jīng)得到預(yù)解析(但是不會(huì)執(zhí)行賦值語句),所以第一次的時(shí)候會(huì)是undefined而不會(huì)報(bào)錯(cuò),執(zhí)行過賦值語句后會(huì)得到3,上段代碼去掉最后一句和下面代碼是一樣的效果。
var a;console.log(a); //undefineda=3;console.log(a); //3然而
如果只是這樣那么JavaScript作用域問題就很簡(jiǎn)單了,然而由于函數(shù)子函數(shù)導(dǎo)致的問題使作用域不止這樣簡(jiǎn)單。大人物登場(chǎng)——執(zhí)行環(huán)境或者說運(yùn)行期上下文(好土鱉):執(zhí)行環(huán)境(execution context)定義了變量或函數(shù)有權(quán)訪問的其它數(shù)據(jù),決定了它們的各自行為。每個(gè)執(zhí)行環(huán)境都有一個(gè)與之關(guān)聯(lián)的變量對(duì)象(variable object, VO),執(zhí)行環(huán)境中定義的所有變量和函數(shù)都會(huì)保存在這個(gè)對(duì)象中,解析器在處理數(shù)據(jù)的時(shí)候就會(huì)訪問這個(gè)內(nèi)部對(duì)象。
全局執(zhí)行環(huán)境是最外層的一個(gè)執(zhí)行環(huán)境,在web瀏覽器中全局執(zhí)行環(huán)境是window對(duì)象,因此所有全局變量和函數(shù)都是作為window對(duì)象的屬性和放大創(chuàng)建的。每個(gè)函數(shù)都有自己的執(zhí)行環(huán)境,當(dāng)執(zhí)行流進(jìn)入一個(gè)函數(shù)的時(shí)候,函數(shù)的環(huán)境會(huì)被推入一個(gè)函數(shù)棧中,而在函數(shù)執(zhí)行完畢后執(zhí)行環(huán)境出棧并被銷毀,保存在其中的所有變量和函數(shù)定義隨之銷毀,控制權(quán)返回到之前的執(zhí)行環(huán)境中,全局的執(zhí)行環(huán)境在應(yīng)用程序退出(瀏覽器關(guān)閉)才會(huì)被銷毀。
作用域鏈
當(dāng)代碼在一個(gè)環(huán)境中執(zhí)行時(shí),會(huì)創(chuàng)建變量對(duì)象的一個(gè)作用域鏈(scope chain,不簡(jiǎn)稱sc)來保證對(duì)執(zhí)行環(huán)境有權(quán)訪問的變量和函數(shù)的有序訪問。作用域第一個(gè)對(duì)象始終是當(dāng)前執(zhí)行代碼所在環(huán)境的變量對(duì)象(VO)
function a(x,y){var b=x+y;return b;}在函數(shù)a創(chuàng)建的時(shí)候它的作用域鏈填入全局對(duì)象,全局對(duì)象中有所有全局變量
如果執(zhí)行環(huán)境是函數(shù),那么將其活動(dòng)對(duì)象(activation object, AO)作為作用域鏈第一個(gè)對(duì)象,第二個(gè)對(duì)象是包含環(huán)境,下一個(gè)是包含環(huán)境的包含環(huán)境。。。。。
function a(x,y){var b=x+y;return b;}var tatal=a(5,10);這時(shí)候 var total=a(5,10);語句的作用域鏈如下
在函數(shù)運(yùn)行過程中標(biāo)識(shí)符的解析是沿著作用域鏈一級(jí)一級(jí)搜索的過程,從第一個(gè)對(duì)象開始,逐級(jí)向后回溯,直到找到同名標(biāo)識(shí)符為止,找到后不再繼續(xù)遍歷,找不到就報(bào)錯(cuò)。
再來看看閉包
之前博客曾經(jīng)總結(jié)道:只要存在調(diào)用內(nèi)部函數(shù)的可能,JavaScript就需要保留被引用的函數(shù)。而且JavaScript運(yùn)行時(shí)需要跟蹤引用這個(gè)內(nèi)部函數(shù)的所有變量,直到最后一個(gè)變量廢棄,JavaScript的垃圾收集器才能釋放相應(yīng)的內(nèi)存空間。回頭再看看好理解了很多,父函數(shù)定義的變量在子函數(shù)的作用域鏈中,子函數(shù)沒有被銷毀,其作用域鏈中所有變量和函數(shù)就會(huì)被維護(hù),不會(huì)被銷毀。
for(var i=0;i<elements.length;i++){elements[i].onclick=function(){alert(i);}}這是上篇博客提到過的經(jīng)典錯(cuò)誤,每次element點(diǎn)擊alert都是length,這段代碼中為element綁定的click事件處理程序的作用域鏈?zhǔn)沁@樣的
由于內(nèi)部函數(shù)(click事件處理程序時(shí)刻有調(diào)用可能),所以其作用域鏈不能被銷毀(更別說本例中i在全局作用域中,只能頁面卸載是銷毀),i的值一直保持for循環(huán)執(zhí)行完后的length值,所以每次觸發(fā)onclick的時(shí)候才會(huì)alert length。
for(var i=0;i<elements.length;i++){(function(n){elements[n].onclick=function(){alert(n);}})(i);}為什么這樣就行了呢,這時(shí)候onclick引用的變量變成了n,而由于立即執(zhí)行函數(shù)的原因,每個(gè)onclick函數(shù)在作用域鏈中分別保持著對(duì)應(yīng)的n(0~length-1),這時(shí)候就可以了。
最后
其實(shí)理解了執(zhí)行環(huán)境和作用域鏈后,閉包翻了變成顯而易見的東西,但是也不能濫用閉包,從上面例子可以看出,閉包會(huì)使子函數(shù)保持其作用域鏈的所有變量及函數(shù)與內(nèi)存中,內(nèi)存消耗很大,在使用的時(shí)候盡量銷毀父函數(shù)不再使用的變量。
(1)作用域
一個(gè)變量的作用域(scope)是程序源代碼中定義的這個(gè)變量的區(qū)域。
1. 在JS中使用的是詞法作用域(lexical scope)
不在任何函數(shù)內(nèi)聲明的變量(函數(shù)內(nèi)省略var的也算全局)稱作全局變量(global scope)
在函數(shù)內(nèi)聲明的變量具有函數(shù)作用域(function scope),屬于局部變量
局部變量?jī)?yōu)先級(jí)高于全局變量
?| 1 2 3 4 5 6 | var name="one"; function test(){ ?var name="two"; ?console.log(name); //two } test(); |
函數(shù)內(nèi)省略var的,會(huì)影響全局變量,因?yàn)樗鼘?shí)際上已經(jīng)被重寫成了全局變量
?| 1 2 3 4 5 6 7 | var name="one"; function test(){ ?name="two"; ?? } test(); console.log(name); //two |
函數(shù)作用域,就是說函數(shù)是一個(gè)作用域的基本單位,js不像c/c++那樣具有塊級(jí)作用域 比如 if for 等
?| 1 2 3 4 5 6 7 8 9 10 | function test(){ ?for(var i=0;i<10;i++){ ??if(i==5){ ???var name = "one"; ??} ?} ?console.log(name); //one } test(); //因?yàn)槭呛瘮?shù)級(jí)作用域,所以可以訪問到name="one" |
當(dāng)然了,js里邊還使用到了高階函數(shù),其實(shí)可以理解成嵌套函數(shù)
?| 1 2 3 4 5 6 7 | function test1(){ ?var name = "one"; ?return function (){ ??console.log(name); ?} } test1()(); |
test1()之后將調(diào)用外層函數(shù),返回了一個(gè)內(nèi)層函數(shù),再繼續(xù)(),就相應(yīng)調(diào)用執(zhí)行了內(nèi)層函數(shù),所以就輸出 ”one"
嵌套函數(shù)涉及到了閉包,后面再談..這里內(nèi)層函數(shù)可以訪問到外層函數(shù)中聲明的變量name,這就涉及到了作用域鏈機(jī)制
2. JS中的聲明提前
js中的函數(shù)作用域是指在函數(shù)內(nèi)聲明的所有變量在函數(shù)體內(nèi)始終是可見的。并且,變量在聲明之前就可以使用了,這種情況就叫做聲明提前(hoisting)
tip:聲明提前是在js引擎預(yù)編譯時(shí)就進(jìn)行了,在代碼被執(zhí)行之前已經(jīng)有聲明提前的現(xiàn)象產(chǎn)生了
比如
?| 1 2 3 4 5 6 7 8 | var name="one"; function test(){ ?console.log(name); //undefined ?var name="two"; ?console.log(name); //two } test(); |
上邊就達(dá)到了下面的效果
?| 1 2 3 4 5 6 7 8 9 | var name="one"; function test(){ ?var name; ?console.log(name); //undefined ?name="two"; ?console.log(name); //two } test(); |
再試試把var去掉?這是函數(shù)內(nèi)的name已經(jīng)變成了全局變量,所以不再是undefined
?| 1 2 3 4 5 6 7 8 | var name="one"; function test(){ ?console.log(name); //one ?name="two"; ?console.log(name); //two } test(); |
3. 值得注意的是,上面提到的都沒有傳參數(shù),如果test有參數(shù),又如何呢?
?| 1 2 3 4 5 6 7 8 9 | function test(name){ ?console.log(name); //one ?name="two"; ?console.log(name); //two } var name = "one"; test(name); console.log(name); // one |
之前說過,基本類型是按值傳遞的,所以傳進(jìn)test里面的name實(shí)際上只是一個(gè)副本,函數(shù)返回之后這個(gè)副本就被清除了。
千萬不要以為函數(shù)里邊的name="two"把全局name修改了,因?yàn)樗鼈兪莾蓚€(gè)獨(dú)立的name
(2)作用域鏈
上面提到的高級(jí)函數(shù)就涉及到了作用域鏈
?| 1 2 3 4 5 6 7 | function test1(){ ?var name = "one"; ?return function (){ ??console.log(name); ?} } test1()(); |
1. 引入一大段話來解釋:
每一段js代碼(全局代碼或函數(shù))都有一個(gè)與之關(guān)聯(lián)的作用域鏈(scope chain)。
這個(gè)作用域鏈?zhǔn)且粋€(gè)對(duì)象列表或者鏈表,這組對(duì)象定義了這段代碼中“作用域中”的變量。
當(dāng)js需要查找變量x的值的時(shí)候(這個(gè)過程稱為變量解析(variable resolution)),它會(huì)從鏈的第一個(gè)對(duì)象開始查找,如果這個(gè)對(duì)象有一個(gè)名為x的屬性,則會(huì)直接使用這個(gè)屬性的值,如果第一個(gè)對(duì)象中沒有名為x的屬性,js會(huì)繼續(xù)查找鏈上的下一個(gè)對(duì)象。如果第二個(gè)對(duì)象依然沒有名為x的屬性,則會(huì)繼續(xù)查找下一個(gè),以此類推。如果作用域鏈上沒有任何一個(gè)對(duì)象含有屬性x,那么就認(rèn)為這段代碼的作用域鏈上不存在x,并最終拋出一個(gè)引用錯(cuò)誤(ReferenceError)異常。
2. 作用域鏈舉例:
在js最頂層代碼中(也就是不包括任何函數(shù)定義內(nèi)的代碼),作用域鏈由一個(gè)全局對(duì)象組成。
在不包含嵌套的函數(shù)體內(nèi),作用域鏈上有兩個(gè)對(duì)象,第一個(gè)是定義函數(shù)參數(shù)和局部變量的對(duì)象,第二個(gè)是全局對(duì)象。
在一個(gè)嵌套的函數(shù)體內(nèi),作用域上至少有三個(gè)對(duì)象。
3. 作用域鏈創(chuàng)建規(guī)則:
當(dāng)定義一個(gè)函數(shù)時(shí)(注意,是定義的時(shí)候就開始了),它實(shí)際上保存一個(gè)作用域鏈。
當(dāng)調(diào)用這個(gè)函數(shù)時(shí),它創(chuàng)建一個(gè)新的對(duì)象來儲(chǔ)存它的參數(shù)或局部變量,并將這個(gè)對(duì)象添加保存至那個(gè)作用域鏈上,同時(shí)創(chuàng)建一個(gè)新的更長(zhǎng)的表示函數(shù)調(diào)用作用域的“鏈”。
對(duì)于嵌套函數(shù)來說,情況又有所變化:每次調(diào)用外部函數(shù)的時(shí)候,內(nèi)部函數(shù)又會(huì)重新定義一遍。因?yàn)槊看握{(diào)用外部函數(shù)的時(shí)候,作用域鏈都是不同的。內(nèi)部函數(shù)在每次定義的時(shí)候都要微妙的差別---在每次調(diào)用外部函數(shù)時(shí),內(nèi)部函數(shù)的代碼都是相同的,而且關(guān)聯(lián)這段代碼的作用域鏈也不相同。
(tip: 把上面三點(diǎn)理解好,記住了,最好還要能用自己的話說出來,不然就背下來,因?yàn)槊嬖嚬倬椭苯訂柲?#xff1a;請(qǐng)描述一下作用域鏈...)
舉個(gè)作用域鏈的實(shí)用例子:
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | var name="one"; function test(){ ?var name="two"; ?function test1(){ ??var name="three"; ??console.log(name); //three ?} ?function test2(){ ??console.log(name); // two ?} ?? ?test1(); ?test2(); } test(); |
上邊是個(gè)嵌套函數(shù),相應(yīng)的應(yīng)該是作用域鏈上有三個(gè)對(duì)象
那么在調(diào)用的時(shí)候,需要查找name的值,就在作用域鏈上查找
當(dāng)成功調(diào)用test1()的時(shí)候,順序?yàn)?test1()->test()->全局對(duì)象window 因?yàn)樵趖est1()上就找到了name的值three,所以完成搜索返回
當(dāng)成功調(diào)用test1()的時(shí)候,順序?yàn)?test2()->test()->全局對(duì)象window 因?yàn)樵趖est2()上沒找到name的值,所以找test()中的,找到了name的值two,就完成搜索返回
還有一個(gè)例子有時(shí)候我們會(huì)犯錯(cuò)的,面試的時(shí)候也經(jīng)常被騙到。
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <script type="text/javascript"> function buttonInit(){ ??for(var i=1;i<4;i++){ ????var b=document.getElementById("button"+i); ????b.addEventListener("click",function(){ ??????alert("Button"+i); //都是 Button4 ????},false); ??} } window.onload=buttonInit; </script> </head> <body> <button id="button1">Button1</button> <button id="button2">Button2</button> <button id="button3">Button3</button> </body> </html> |
為什么?
根據(jù)作用域鏈中變量的尋找規(guī)則:
| 1 2 3 | b.addEventListener("click",function(){ ??????alert("Button"+i); ????},false); |
這里有一個(gè)函數(shù),它是匿名函數(shù),既然是函數(shù),那就在作用域鏈上具有一個(gè)對(duì)象,這個(gè)函數(shù)里邊使用到了變量i,它自然會(huì)在作用域上尋找它。
查找順序是 這個(gè)匿名函數(shù) -->外部的函數(shù)buttonInit() -->全局對(duì)象window
匿名函數(shù)中找不到i,自然跑到了buttonInit(), ok,在for中找到了,
這時(shí)注冊(cè)事件已經(jīng)結(jié)束了,不要以為它會(huì)一個(gè)一個(gè)把i放下來,因?yàn)楹瘮?shù)作用域之內(nèi)的變量對(duì)作用域內(nèi)是一直可見的,就是說會(huì)保持到最后的狀態(tài)
當(dāng)匿名函數(shù)要使用i的時(shí)候,注冊(cè)事件完了,i已經(jīng)變成了4,所以都是Button4
那怎么解決呢?
給它傳值進(jìn)去吧,每次循環(huán)時(shí),再使用一個(gè)匿名函數(shù),把for里邊的i傳進(jìn)去,匿名函數(shù)的規(guī)則如代碼
?| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <script type="text/javascript"> function buttonInit(){ ??for(var i=1;i<4;i++){ ????(function(data_i){ ????var b=document.getElementById("button"+data_i); ????b.addEventListener("click",function(){ ??????alert("Button"+data_i); ????},false); ????})(i); ??} } window.onload=buttonInit; </script> </head> <body> <button id="button1">Button1</button> <button id="button2">Button2</button> <button id="button3">Button3</button> </body> </html> |
這樣就可以 Button1..2..3了
4.上述就是作用域鏈的基本描述,另外,with語句可用于臨時(shí)拓展作用域鏈(不推薦使用with)
語法形如:
with(object)
statement
這個(gè)with語句,將object添加到作用域鏈的頭部,然后執(zhí)行statement,最后把作用域鏈恢復(fù)到原始狀態(tài)
簡(jiǎn)單用法:
比如給表單中各個(gè)項(xiàng)的值value賦值
一般可以我們直接這樣
| 1 2 3 4 | var f = document.forms[0]; f.name.value = ""; f.age.value = ""; f.email.value = ""; |
引入with后(因?yàn)槭褂脀ith會(huì)產(chǎn)生一系列問題,所以還是使用上面那張形式吧)
?| 1 2 3 4 5 | with(document.forms[0]){ f.name.value = ""; f.age.value = ""; f.email.value = ""; } |
另外,假如 一個(gè)對(duì)象o具有x屬性,o.x = 1;
那么使用
| 1 2 3 | with(o){ ?x = 2; } |
就可以轉(zhuǎn)換成 o.x = 2;
假如o沒有定義屬性x,它的功能就只是相當(dāng)于 x = 2; 一個(gè)全局變量罷了。
因?yàn)閣ith提供了一種讀取o的屬性的快捷方式,但他并不能創(chuàng)建o本身沒有的屬性。
要理解變量的作用域范圍就得先理解作用域鏈
用var關(guān)鍵字聲明一個(gè)變量時(shí),就是為該變量所在的對(duì)象添加了一個(gè)屬性。
作用域鏈:由于js的變量都是對(duì)象的屬性,而該對(duì)象可能又是其它對(duì)象的屬性,而所有的對(duì)象都是window對(duì)象的屬性,所以這些對(duì)象的關(guān)系可以看作是一條鏈
鏈頭就是變量所處的對(duì)象,鏈尾就是window對(duì)象
看下面的代碼:
function t() {
var a;
function t2() {
var b;
}
}
js中函數(shù)也是對(duì)象,所以變量a所在的對(duì)象是t,t又在window對(duì)象中,所以a的作用域鏈如下
t--window
那么b所以在的對(duì)象即t2,t2又包含在t中,t又在window對(duì)象,所以b的作用域鏈如下
t2--t--window
明白了作用域鏈下面就開始變量的作用域分析了
1 javascript 沒有var的變量都為全局變量,且為window對(duì)象的屬性
復(fù)制代碼 代碼如下:
function test1() {
//執(zhí)行這個(gè)句的時(shí)候它會(huì)找作用域?qū)ο?#xff0c;這個(gè)函數(shù)就是作用域鏈中的第一個(gè)對(duì)象,但這個(gè)對(duì)象中沒有相關(guān)的var語句
//于里就找作用域鏈的第二個(gè)對(duì)象,即全局對(duì)象,而全局對(duì)象中也沒有相關(guān)的var語句
//由于沒有相關(guān)的var語句,js隱式在函數(shù)地聲明了變量即var all;
all = 30;
alert(all);
}
test1();
alert(all);
alert(window.all);
2 函數(shù)內(nèi)(函數(shù)內(nèi)的函數(shù)除外)定義的變量在整個(gè)函數(shù)內(nèi)部都有效
復(fù)制代碼 代碼如下:
function test2() {
var t = 0;
//在for的條件里定義變量,這個(gè)變更的作用域鏈對(duì)象是這個(gè)函數(shù)
//因此在整個(gè)的函數(shù)里它是有效的
for (var i = 0; i < 5; i++) {
t += i;
}
alert(i);
}
test2();
3 函數(shù)內(nèi)部的變量取代全局同名變量
復(fù)制代碼 代碼如下:
var t = "bb";
function test() {
//執(zhí)行t的時(shí)候,它會(huì)先找作用域鏈對(duì)象,由于它定義在函數(shù)內(nèi)部,所以這個(gè)函數(shù)就是它的作用域鏈的第一個(gè)對(duì)象
//而在這個(gè)對(duì)象里又有t的定義,所以t就是局部變量了,它替換了全局變量t
//t只是此時(shí)有定義,但并沒有賦值,賦值在下一行,所以這里輸出了undefined
alert(t);
var t = "aa";
alert(t);
}
test();
4 沒塊的作用域
復(fù)制代碼 代碼如下:
if (true) {
//在塊中定義了一個(gè)變量,它的作用域鏈的第一個(gè)對(duì)象就是全局對(duì)象window
var tmp = 0;
}
//tmp的作用域鏈的第一個(gè)對(duì)象就是全局對(duì)象window,而上面又有全局對(duì)象中相關(guān)的var語句,因此輸出0
alert(tmp);
以下內(nèi)容來自讀網(wǎng)上博客的總結(jié),當(dāng)筆記使用,只記重點(diǎn),同時(shí)非常感謝樂于分享的博主們,是你們讓我站在了巨人的肩旁上!
1、
復(fù)制代碼 代碼如下:
var temp = (function(){
var name ="test";
return function(){
?alert(name);
}
})();
以上代碼片斷是我們jser經(jīng)常見到的寫法,是傳說中的閉包。 眾所周知:調(diào)用 temp();會(huì)彈出 “ test”;該過程可以有以下三條理論作為依據(jù)來解釋:
1)js 作用域只和函數(shù)的界定符相關(guān),函數(shù)與函數(shù)的嵌套形成了作用域鏈;
2)作用域鏈的創(chuàng)建規(guī)則是復(fù)制上一層環(huán)境的作用域鏈,并將指向本環(huán)境變量對(duì)象的指針放到鏈?zhǔn)?#xff1b;
3)在Javascript中,如果一個(gè)對(duì)象不再被引用,那么這個(gè)對(duì)象就會(huì)被GC回收。如果兩個(gè)對(duì)象互相引用,而不再被第3者所引用,那么這兩個(gè)互相引用的對(duì)象也會(huì)被回收。
如果看了以上3條還不明白,可看接下來結(jié)合理論對(duì)代碼的詳細(xì)解釋:
首先外層函數(shù)執(zhí)行完,被銷毀;但是外層函數(shù)的作用域鏈被復(fù)制到內(nèi)層函數(shù)的作用域鏈里,組成內(nèi)層函數(shù)的作用域鏈的一部分,記住是復(fù)制,不是引用(依據(jù)第2條),所以內(nèi)層函數(shù)仍然可以訪問到 name;由于 返回的內(nèi)層函數(shù)被 temp 引用,所以當(dāng)外層函數(shù)執(zhí)行完被銷毀后,內(nèi)層函數(shù)雖然作為外層函數(shù)的一部分,但是依然存在,正如第3條依據(jù)那樣,它被第三者引用了;傳說中的閉包也就是這個(gè)理
總結(jié)
以上是生活随笔為你收集整理的js作用域及作用域链概念理解及使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Javascript 进阶 作用域 作用
- 下一篇: javascript深入理解js闭包