一直對Js的作用域有點迷糊,今天偶然讀到javaScript權威指南,立馬被吸引住了,寫的真不錯。我看的是第六版本,相當的厚,大概1000多頁,Js博大精深,要熟悉精通需要大毅力大功夫。
一:函數作用域
先看一小段代碼:
[Javascript] view plain copy PRint?var scope=“global”; function t(){ console.log(scope); var scope=“local” console.log(scope); } t();var scope="global";function t(){ console.log(scope); var scope="local" console.log(scope);}t();(PS: console.log()是firebug提供的調試工具,很好用,有興趣的童鞋可以用下,比瀏覽器+alert好用多了)
第一句輸出的是: “undefined”,而不是 “global”
第二講輸出的是:”local”
你可能會認為第一句會輸出:”global”,因為代碼還沒執行var scope=”local”,所以肯定會輸出“global”。
我說這想法完全沒錯,只不過用錯了對象。我們首先要區分Javascript的函數作用域與我們熟知的C/C++等的塊級作用域。
在C/C++中,花括號內中的每一段代碼都具有各自的作用域,而且變量在聲明它們的代碼段之外是不可見的。而Javascript壓根沒有塊級作用域,而是函數作用域.
所謂函數作用域就是說:-》變量在聲明它們的函數體以及這個函數體嵌套的任意函數體內都是有定義的。
所以根據函數作用域的意思,可以將上述代碼重寫如下:
[javascript] view plain copy print?var scope=“global”; function t(){ var scope; console.log(scope); scope=”local” console.log(scope); } t();var scope="global";function t(){ var scope; console.log(scope); scope="local" console.log(scope);}t();我們可以看到,由于函數作用域的特性,局部變量在整個函數體始終是由定義的,我們可以將變量聲明”提前“到函數體頂部,同時變量初始化還在原來位置。
為什么說Js沒有塊級作用域呢,有以下代碼為證:
[javascript] view plain copy print?var name=“global”; if(true){ var name=“local”; console.log(name) } console.log(name);var name="global";if(true){ var name="local"; console.log(name)}console.log(name);都輸出是“local”,如果有塊級作用域,明顯if語句將創建局部變量name,并不會修改全局name,可是沒有這樣,所以Js沒有塊級作用域。現在很好理解為什么會得出那樣的結果了。scope聲明覆蓋了全局的scope,但是還沒有賦值,所以輸出:”undefined“。
所以下面的代碼也就很好理解了。
[javascript] view plain copy print?function t(flag){ if(flag){ var s=“ifscope”; for(var i=0;i<2;i++) ; } console.log(i); console.log(s); } t(true);function t(flag){ if(flag){ var s="ifscope"; for(var i=0;i<2;i++) ; } console.log(i); console.log(s);}t(true);輸出:2 ”ifscope”二:變量作用域
還是首先看一段代碼:
[javascript] view plain copy print?function t(flag){ if(flag){ s=”ifscope”; for(var i=0;i<2;i++) ; } console.log(i); } t(true); console.log(s);function t(flag){ if(flag){ s="ifscope"; for(var i=0;i<2;i++) ; } console.log(i);}t(true);console.log(s);就是上面的翻版,知識將聲明s中的var去掉。程序會報錯還是輸出“ifscope”呢?
讓我揭開謎底吧,會輸出:”ifscope”
這主要是Js中沒有用var聲明的變量都是全局變量,而且是頂層對象的屬性。
所以你用console.log(window.s)也是會輸出“ifconfig”
當使用var聲明一個變量時,創建的這個屬性是不可配置的,也就是說無法通過delete運算符刪除
var name=1 ->不可刪除
sex=”girl“ ->可刪除
this.age=22 ->可刪除
三:作用域鏈
先來看一段代碼:
[javascript] view plain copy print?name=“lwy”; function t(){ var name=“tlwy”; function s(){ var name=“slwy”; console.log(name); } function ss(){ console.log(name); } s(); ss(); } t();name="lwy";function t(){ var name="tlwy"; function s(){ var name="slwy"; console.log(name); } function ss(){ console.log(name); } s(); ss();}t();當執行s時,將創建函數s的執行環境(調用對象),并將該對象置于鏈表開頭,然后將函數t的調用對象鏈接在之后,最后是全局對象。然后從鏈表開頭尋找變量name,很明顯name是”slwy”。
但執行ss()時,作用域鏈是: ss()->t()->window,所以name是”tlwy”
下面看一個很容易犯錯的例子:
[html] view plain copy print?<html> <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);},false); } } window.onload=buttonInit; </script> </head> <body> <button id=“button1”>Button1</button> <button id=“button2”>Button2</button> <button id=“button3”>Button3</button> </body> </html><html><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);},false); }}window.onload=buttonInit;</script></head><body><button id="button1">Button1</button><button id="button2">Button2</button><button id="button3">Button3</button></body></html> 當文檔加載完畢,給幾個按鈕注冊點擊事件,當我們點擊按鈕時,會彈出什么提示框呢?很容易犯錯,對是的,三個按鈕都是彈出:”Button4”,你答對了嗎?
當注冊事件結束后,i的值為4,當點擊按鈕時,事件函數即function(){ alert(“Button”+i);}這個匿名函數中沒有i,根據作用域鏈,所以到buttonInit函數中找,此時i的值為4,
所以彈出”button4“。
四:with語句
說到作用域鏈,不得不說with語句。with語句主要用來臨時擴展作用域鏈,將語句中的對象添加到作用域的頭部。
看下面代碼
[javascript] view plain copy print?person={name:“yhb”,age:22,height:175,wife:{name:“lwy”,age:21}}; with(person.wife){ console.log(name); }person={name:"yhb",age:22,height:175,wife:{name:"lwy",age:21}};with(person.wife){ console.log(name);}with語句將person.wife添加到當前作用域鏈的頭部,所以輸出的就是:“lwy”.with語句結束后,作用域鏈恢復正常。
新聞熱點
疑難解答