前面的文章說到, 執行上下文的建立階段,主要有三個內容:javascript
一、建立變量對象;二、初始化做用域鏈;三、肯定this的指向。java
在這裏,要說一下做用域和做用域鏈了,先來一個例子:git
//全局環境 var a = 10; function inner(){ console.log(a); } inner();
在inner函數的執行上下文的執行階段中,它的VO(變量對象)都沒有var a這樣的變量聲明,因此console的時候,怎樣得到a的值呢,就是經過全局環境中的AO(活動對象),由於裏面就有a的值(不知道什麼是VO和AO,必定要先看一下我前面的文章:關於javascript中的變量對象和活動對象)。github
其實,做用域這個東西,能夠理解爲自身執行上下文中的活動對象(AO)能夠被訪問的區域,說的有點拗口,其實看一下我前面的文章(關於javascript中的變量對象和活動對象),就能夠知道,其實咱們執行函數的時候,用到的變量值,都是從AO上面取到的,若是本身的執行上下文中的AO沒有對應要用的值(例如上面例子中的a),那就要往上一層的執行上下文中的AO中找這個值,若是上一層尚未,就要再往上一層的執行上下文中的AO去找,而這個一層一層的連接關係,就是所謂的做用域鏈。(這裏說到的上一層,其實就是執行上下文棧中壓着的下一層執行上下文,不理解能夠先看我前面的文章:關於javascript中的從堆棧內存到執行上下文)函數
說到做用域這個東西,我以爲很多人都被它坑過,舉個例子:this
//先聲明變量jj並賦值爲10 var jj = 10; //再聲明一個函數what function what(){ console.log(jj); } //執行what函數 what();
相信你們都很是清楚打印結果了,就和上面例子同樣,就是10。那若是這樣呢:code
//先聲明變量jj並賦值爲10 var jj = 10; //再聲明一個函數what function what(){ console.log(jj); var jj = 20; console.log(jj); } //執行what函數 what();
是否是會說打印結果是10和20呢?那就錯了,實際打印結果是undefined和20。爲何呢?不是一開始打印時候前面沒有變量jj,而後向上找到等於10,後面就改變它的值,而後輸入20嗎?對象
這樣就沒有真正理解javascript的詞法做用域的概念。做用域的類別能夠影響到變量的取值,分爲詞法做用域(靜態做用域)和動態做用域。ip
它們的區別是:對於詞法做用域,函數的做用域在函數定義的時候就已經肯定了,而動態做用域不一樣,在函數調用的時候才肯定的。內存
而javascript,採用的就是詞法做用域,或者叫靜態做用域。
因此在what函數中聲明瞭一個var jj = 20,就將裏面有jj這個變量名的取值,框住了在這個函數裏面了,或者能夠說,調用what函數的時候,你用var這樣的字眼聲明瞭jj這個變量,就會在執行上下文建立時候的變量對象VO中掛上了屬性jj=undefined,因此一開始就將jj打印出來,因爲尚未賦值,因此打印出undefined了,而後後面賦值了,就打印出了20了。
若是你想按照你一開始想的那樣打印出10和20,能夠將what函數裏面的var jj = 20改成jj = 20,去掉var,這樣就至關於what函數裏面沒有聲明變量jj,而是向上找到jj,並將它打印,而後更改jj的值,再打印,實際上,這種作法會污染全局變量,由於你在what函數裏面將jj這個全局變量的值改成20了。
好了,若是你明白由於用var聲明瞭變量,致使在自身的執行上下文中尋找jj的值而不是向上尋找,可是你不明白爲何var jj 明明在console以後才聲明的,爲何會受到它影響呢?這裏,就要再說一個概念,叫作變量提高。
變量提高,就是解釋器會將函數聲明和變量聲明提高到方法體的最頂部,函數聲明比變量聲明提得更高。
其實很容易理解變量提高,仍是回去看一下我前面的文章(關於javascript中的變量對象和活動對象)就知道了,執行上下文在建立的時候就會建立變量對象,而變量對象的建立順序爲:形參、函數聲明、變量聲明(用var 聲明的),因此在你的代碼執行階段(執行上下文的執行階段)以前,它已經建立了變量對象了,因此相對其餘的執行代碼來講,這就是所謂的變量提高。
說回去最初的執行what函數的地方,其實我這樣寫也是能夠的:
//先聲明變量jj並賦值爲10 var jj = 10; //執行what函數 what(); //如今才聲明一個函數what function what(){ console.log(jj); }
爲何呢?由於變量提高,解釋器會將聲明的what這個函數提到頂部,因此你上面執行what這個函數,實際解釋器已經將what函數提高上去了。
除了函數聲明,變量聲明也同樣。
回到前面的例子,我在what函數內聲明並初始化var jj = 20 能夠當作兩個步驟,第一個步驟,聲明變量var jj ,第二個步驟,初始化變量,jj = 20,因此上面的函數能夠寫成這樣:
function what(){ console.log(jj); var jj ; jj = 20; console.log(jj); }
固然了,這裏面聲明的jj變量,也會變量提高,因此會變成這樣:
function what(){ var jj ; console.log(jj); jj = 20; console.log(jj); }
再結合回到前面一塊兒:
//先聲明變量jj並賦值爲10 var jj = 10; //再聲明一個函數what function what(){ var jj ; console.log(jj); jj = 20; console.log(jj); } //執行what函數 what();
是否是很好地理解了打印結果就是undefined 和 20了,這裏要注意的是,初始化變量是不會提高的,因此jj = 20仍是留在了原位。
換個方式說一下變量提高,下面兩個函數寫法有什麼不一樣的地方:
//寫法一 var claim = function(){ console.log('i am first'); }; //寫法二 function claim(){ console.log('i am first'); }
舉一個例子就很清楚了:
//寫法一 var claim = function(){ console.log('i am first'); }; claim();//打印結果爲i am first var claim = function(){ console.log('i am second'); }; claim();//打印結果爲i am second
//寫法二 function claim (){ console.log('i am first'); }; claim();//打印結果爲i am second function claim(){ console.log('i am second'); }; claim();//打印結果爲i am second
好了,理解了上面兩種打印結果就知道了變量提高了。
其實做用域前面已經說得很清楚了,就是執行上下文的AO(活動對象)可被訪問的範圍,而做用域鏈能夠類比原型鏈,本身若是沒有,就一級一級往上找,這個一級一級,就是執行上下文棧中壓着的下一個執行上下文(再回顧前面文章:關於javascript中的從堆棧內存到執行上下文),那就很容易理解明白了。