大多數時候,咱們對做用域產生混亂的主要緣由是分不清楚應該按照函數位置的嵌套順序,仍是按照函數的調用順序進行變量查找。再加上this機制的干擾,使得變量查找極易出錯。這其實是由兩種做用域工做模型致使的,做用域分爲詞法做用域和動態做用域,分清這兩種做用域模型就可以對變量查找過程有清晰的認識。本文是深刻理解javascript做用域系列第二篇——詞法做用域和動態做用域javascript
第一篇介紹過,編譯器的第一個工做階段叫做分詞,就是把由字符組成的字符串分解成詞法單元。這個概念是理解詞法做用域的基礎html
簡單地說,詞法做用域就是定義在詞法階段的做用域,是由寫代碼時將變量和塊做用域寫在哪裏來決定的,所以當詞法分析器處理代碼時會保持做用域不變java
關係函數
不管函數在哪裏被調用,也不管它如何被調用,它的詞法做用域都只由函數被聲明時所處的位置決定this
function foo(a) { var b = a * 2; function bar(c) { console.log( a, b, c ); } bar(b * 3); } foo( 2 ); // 2 4 12
在這個例子中有三個逐級嵌套的做用域。爲了幫助理解,能夠將它們想象成幾個逐級包含的氣泡spa
做用域氣泡由其對應的做用域塊代碼寫在哪裏決定,它們是逐級包含的code
氣泡1包含着整個全局做用域,其中只有一個標識符:foohtm
氣泡2包含着foo所建立的做用域,其中有三個標識符:a、bar和b對象
氣泡3包含着bar所建立的做用域,其中只有一個標識符:cblog
查找
做用域氣泡的結構和互相之間的位置關係給引擎提供了足夠的位置信息,引擎用這些信息來查找標識符的位置
在代碼片斷中,引擎執行console.log(...)聲明,並查找a、b和c三個變量的引用。它首先從最內部的做用域,也就是bar(...)函數的做用域開始查找。引擎沒法在這裏找到a,所以會去上一級到所嵌套的foo(...)的做用域中繼續查找。在這裏找到了a,所以引擎使用了這個引用。對b來說也同樣。而對c來講,引擎在bar(...)中找到了它
[注意]詞法做用域查找只會查找一級標識符,若是代碼引用了foo.bar.baz,詞法做用域查找只會試圖查找foo標識符,找到這個變量後,對象屬性訪問規則分別接管對bar和baz屬性的訪問
foo = { bar:{ baz: 1 } }; console.log(foo.bar.baz);//1
遮蔽
做用域查找從運行時所處的最內部做用域開始,逐級向外或者說向上進行,直到碰見第一個匹配的標識符爲止
在多層的嵌套做用域中能夠定義同名的標識符,這叫做「遮蔽效應」,內部的標識符「遮蔽」了外部的標識符
var a = 0; function test(){ var a = 1; console.log(a);//1 } test();
全局變量會自動爲全局對象的屬性,所以能夠不直接經過全局對象的詞法名稱,而是間接地經過對全局對象屬性的引用來對其進行訪問
var a = 0; function test(){ var a = 1; console.log(window.a);//0 } test();
經過這種技術能夠訪問那些被同名變量所遮蔽的全局變量。但非全局的變量若是被遮蔽了,不管如何都沒法被訪問到
javascript使用的是詞法做用域,它最重要的特徵是它的定義過程發生在代碼的書寫階段
那爲何要介紹動態做用域呢?實際上動態做用域是javascript另外一個重要機制this的表親。做用域混亂多數是由於詞法做用域和this機制相混淆,傻傻分不清楚
動態做用域並不關心函數和做用域是如何聲明以及在任何處聲明的,只關心它們從何處調用。換句話說,做用域鏈是基於調用棧的,而不是代碼中的做用域嵌套
var a = 2; function foo() { console.log( a ); } function bar() { var a = 3; foo(); } bar();
【1】若是處於詞法做用域,也就是如今的javascript環境。變量a首先在foo()函數中查找,沒有找到。因而順着做用域鏈到全局做用域中查找,找到並賦值爲2。因此控制檯輸出2
【2】若是處於動態做用域,一樣地,變量a首先在foo()中查找,沒有找到。這裏會順着調用棧在調用foo()函數的地方,也就是bar()函數中查找,找到並賦值爲3。因此控制檯輸出3
兩種做用域的區別,簡而言之,詞法做用域是在定義時肯定的,而動態做用域是在運行時肯定的