javascript變數的範圍

來源:互聯網
上載者:User

不太會寫冠冕堂皇的開場白,直接進入主題。

我們看一道題,出處為javaeye的某貼——這世界就是這樣,有些人喜歡製造問題,有人喜歡解決問題。製造問題的人為解決問題的人帶來就業機會……

var a=100;  var b=true;  function test(){      alert(a);      alert(b);      b=false;      alert(b);      var a=200;      alert(a/2);      alert(++Math.PI);      alert(Math.PI++);  }  test();

<br /><script type="text/javascript"><br />var a=100;<br />var b=true;<br />function test(){<br /> alert(a);<br /> alert(b);<br /> b=false;<br /> alert(b);<br /> var a=200;<br /> alert(a/2);<br /> alert(++Math.PI);<br /> alert(Math.PI++);<br />}<br />test();<br /></script><br />

運行代碼

為什麼第一個alert為undefined,而第二個為true。這問題也可以延伸為——alert(b)時怎麼就會找外部的b,而alert(a)時就不會往外面找?!

我們都明白局部變數的優先順序大於全域變數,或者說內圍範圍的變數的優先順序比外圍的高。當JS引擎在當前範圍找不到此變數時,它就往外圍的範圍找。不過,在這之前,有一個嚴肅的問題是,究竟當前範圍存不存在這個變數。像javascript這樣的解釋型語言,基本分為兩個階段,編譯期(下面為符合大多數語言的稱呼習慣,改叫先行編譯)與運行期。在先行編譯階段,它是用函數來劃分範圍,然後逐層為其以 var 聲明的變數(下略稱為var變數)與函數定義開闢記憶體空間,再然後對var變數進行特殊處理,統統賦初始值為undefined,如:

由,我們便可以推知,當前網頁擁有兩個a,一個b,一個test函數。如果在運行期用到除此以外的東東,如c函數或d變數啦,就會報未定義錯誤(用eval等非正常手段產生變數與函數的情況除外),此外,它們最多出現未賦值警告。

javascript的運行期是在為var變數與函數定義分配空間後立即執行,並且是逐行往下執行的。

  • 第1行它為外圍範圍的a賦值為100
  • 第2行它為外圍範圍的b賦值為true
  • 第3行進行test的範圍,我們簡稱為內圍範圍。
  • 第4行就立即調用內圍範圍的a,這時它還沒有來得及賦值呢!不過它已經聲明過了,因此預設為其賦值為undefined(在先行編譯階段,見圖),於是alert為undefined
  • 第5行就調用b時,JS引擎就拿起我畫的圖看了(笑),發現test的範圍內沒有b,眼睛往外望,發現b了,而b在第二行就賦值為true,於是alert為true。
  • 第6行為一個賦值操作,把外圍的b變數改賦為false。於是到第7行時,alert為false。以下說法不說了。

作為對比,我們改寫一下例子:

var a=100;  var b=true;  function test(){      alert(a);      alert(b);      var b=false;      alert(b);      var a=200;      alert(a/2);      alert(++Math.PI);      alert(Math.PI++);  }  test();

這時在test函數的範圍內,b也被聲明了。

掌握先行編譯為var變數與函數定義分配空間這一事實後,許多問題就迎刃而解。我們看犀牛書上的一個例子。

var scope = "global";  function f() {      alert(scope);                  var scope = "local";           alert(scope);              }  f(); 

答案呼之欲出!

我們來看更複雜的例子。

Object.prototype.test = 'wrong';var test = 'right';(function f() {    alert(test);})();

這個問題的痛點在於,運行期時,又產生一同名變數,它是附著於Object.prototype,究竟哪一個距離F()的範圍近一些呢?!測試結果是var test。於是我們有了:

於是我們明白了,原來定義在函數外面的var變數並不位於window範圍的下一層。

我們繼續加深難度。

(function f() {    alert(test);})();Object.prototype.test = 'ccc';Object.test = "bbb"window.test = "aaa";

報未定義錯誤,因為先行編譯期時沒有符合要求的var變數,而在運行期時,和它同名的變數在調用時(第2行)也尚未建立起來!

如果這樣呢?!

Object.test = "bbb";Object.prototype.test = 'ccc';window.test = "aaa";(function f() {    alert(test);})();

估計有很多人猜是bbb,其實是ccc!有人就不解了,不是對象的屬性的優先順序比其原型屬性的優先順序高嗎???

無錯,的確如此,不過對象的屬性是這樣定義的:

var o = {test:"eee"}

Object.test = “XXX”這樣定義,為類屬性,或稱靜態屬性。類屬性優先順序都是比執行個體屬性低的

通過這圖也教育我們,一定要用局部變數啊,要不,一層層往上爬,效率是多麼低啊。另外,這圖也告訴我們,window是一個多麼進階的存在啊(微軟最愛聽),Object都比它低一等,更別提什麼繼承問題啦!(在FF與IE中)

//顛覆常識的存在alert(window instanceof Object);

<br /><script type="text/javascript"><br />//顛覆常識的存在<br />alert(window instanceof Object);<br /></script><br />

運行代碼

搞定這個我們看最難的一題。類屬性,執行個體屬性,原型屬性,極晚綁定的屬性都考到了!不過執行個體屬性與類屬性已超出本文的討論範圍,恕不討論了。這些也不太難,很容易google到的,自己google吧。

function foo(){  foo.abc = function(){alert('def')}  this.abc = function(){alert('xyz')}  abc = function(){alert('@@@@@')};  var abc = function(){alert('$$$$$$')}}foo.prototype.abc = function(){alert('456');}foo.abc = function(){alert('123');}var f = new foo();f.abc();foo.abc();abc();

藍色理想的人說這題出得不好,有錯。我說,有錯才好,這樣才能考出水平!十秒內做出正確答案,說明學會了。

答案:

<br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><br /><p>第一個為xyz,f.abc很明顯就是調用其執行個體屬性,通常建構函式是這樣定義執行個體方法與執行個體屬性的:</p><br /><blockquote>this.XXX = "XXXXXXXX"</blockquote><br /><p>第二個就是建構函式內部的foo.abc還是外部的foo.abc的問題了。我們只要搞清楚一點就可以,寫在外面的那個是極晚綁定的成員,優先順序極低,覆蓋不了內部的同名成員。</p><br /><p>第三個為什麼會報錯呢?!因為在先行編譯階段,foo()的範圍就有了一個abc變數,<br />因此abc = function(){alert('@@@@@')}就變不了window.abc = function(){alert('@@@@@')},而下面那一行就更甭提了。<br />而我們調用的是abc,全域變數的abc,瀏覽器會自動給它加上window.abc,就算不加上,沿途的Objcet也沒有abc這個變數,因此它在f()的範圍外找不到abc,就報未定義錯誤!</p><br />

運行代碼

最後歸納一下,JS引擎有兩個設定變數的機會。第一次在先行編譯時期,所有var變數會分配到各自的範圍中,值一律為undefined。第二次在運行期,由於是逐行執行,因此是可變的。我們可以通過eval與Function動態產生新的變數,它們的範圍都是可制定的,其他指派陳述式,只是把變數固定於頂層範圍(window)中,或是僅僅是重新賦值。我們也可以用delete來刪除對象的屬性,迫使其往外走同名變數。with閉包會在其引用的對象的屬性被刪除後,在閉包的外圍尋找與此屬性同名的變數。有關with閉包請看我另一篇博文。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.