關於javascript中的做用域和做用域鏈

關於javascript中的做用域和做用域鏈

我GitHub上的菜鳥倉庫地址: 點擊跳轉查看其餘相關文章
文章在個人博客上的地址: 點擊跳轉

        前面的文章說到, 執行上下文的建立階段,主要有三個內容: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中的從堆棧內存到執行上下文),那就很容易理解明白了。

相關文章
相關標籤/搜索