作用域

本文不進行系統的講解,只枚舉及個特殊的例子。
例1

<script>
    var str1 = "hello";
    var str2 = "world";

    function t1() {
        console.log(str1);
        console.log(str2);

        var str2 = "toby";
        console.log(str2);
    }

    //這裏會輸出什麼?
    t1();

</script>

結果:

  • hello
  • undefined
  • tbody
    你一定奇怪爲什麼是undefined,因爲根據函數ti來說,他是有自己的str2變量的,只不過聲明在試用之後,所以是undefined。
    例2
<script>
    function t(userName) {
        console.log(userName);//這裏輸出什麼?

        function userName() {
            console.log('tom');
        }
    }
    t('toby');
</script>

你一定以爲這裏輸出的是tbody,但實際處出結果是:

 function userName() {
            console.log('tom');
        }

執行t(‘toby’)的時候,會開始兩個階段,一個是分析階段,分析完就到執行階段

分析階段:

函數運行的瞬間,會生成一個Active Object對象(以下簡稱AO對象),一個函數作用域內能找到的所有變量,都在AO上,此時用代碼表示爲: t.AO = {}

分析參數: 接收參數,以參數名爲屬性,參數值爲屬性值,因爲沒有參數,因此分析結果用代碼表示爲: t.AO = {userName : toby}

分析var聲明: t函數內沒有var聲明,略過

分析函數聲明: 這個函數聲明有個特點,AO上如果有與函數名同名的屬性,則會被此函數覆蓋,因爲函數在JS領域,也是變量的一種類型,因此用代碼表示爲: t.AO = { userName : function userName() {console.log(‘tom’);}}

執行階段:

執行t(‘toby’)的時候,當執行到console.log(userName)時,就調用t.AO.userName,所以,最後的輸出結果是function userName() {console.log(‘tom’);}
所以重點就在於,同一域內,同名的函數和變量,函數優先,也就是函數會覆蓋變量。
例3:

<script>
    function t(userName) {
        console.log(userName);//這裏輸出什麼?

        var userName = function () {
            console.log('tom');
        }
        console.log(userName);//這裏輸出什麼
    }
    t('toby');
</script>

結果:

  • tbody
  • function () {
    console.log(‘tom’);
    }

分析階段:

創建AO對象,t.AO = {}

分析參數: t.AO = {userName : toby}

分析var聲明: 在AO上,形成一個屬性,以var的變量名爲屬性名,值爲undefined,(因爲是先分析,後執行,這只是詞法分析階段,並不是執行階段,分析階段值都是undefined,如果執行階段有賦值操作,那值會按照正常賦值改變),也就是說代碼應該表示爲:t.AO = {userName : undefined},但是還有另外一個原則,那就是如果AO有已經有同名屬性,則不影響(也就是什麼事都不做),由於分析參數時,AO上已經有userName這個屬性了,所以按照這個原則,此時什麼事都不做,也就是說,此時按照分析參數時的結果t.AO = {userName : toby}

分析函數聲明: 此時沒有函數聲明,略過

執行階段:

調用t.AO.userName,所以,最後的輸出結果是toby

例4:

<script>
    t();
    t2();

    function t() {
        console.log('toby');//這裏會輸出什麼?
    }

    var t2 = function () {
        console.log('hello toby');//這裏會輸出什麼?
    };
</script>

結果:tbody 報錯。因爲第一個已解析的時候已經將整個函數提升到頂部,但是第二個相當於變量,只有聲明,沒有定義。
例5:

<script>
    function t(userName) {
        console.log(userName);//這裏輸出什麼?
        function userName() {
            console.log(userName);//這裏輸出什麼?
        }
        userName();
    }
    t('toby');
</script>

結果:

  • function userName() {console.log(userName);}}
  • function userName() {console.log(userName);}}
    t(‘toby’)的分析和執行階段

分析階段:

創建AO對象,t.AO = {}

分析參數: t.AO = {userName : toby}

分析var聲明: 有同名屬性,不做任何事,還是t.AO = {userName : toby}

分析函數聲明: 有同名屬性,覆蓋: t.AO = {userName : function userName() {console.log(userName);}}

執行階段:

t.AO.userName 輸出爲function userName() {console.log(userName);}}

userName()的分析和執行階段

這裏也要搞清楚兩個概念

//執行userName()分析的是
function () {
console.log(userName);
};

//而不是
var userName = function () {
console.log(userName);
};

分析階段:

創建AO對象,userName.AO = {}

分析參數: 無,略過

分析var聲明: 無,略過

分析函數聲明: 無,略過

執行階段:

因爲此時userName.AO = {}是個空對象,無法執行userName.AO.userName,所以會向上一層找,所以輸出t.AO.userName的結果,也就是function userName() {console.log(userName);}}

例6:

<script>
    function t(userName) {
        console.log(userName);//這裏輸出什麼?
        var userName = function () {
            console.log(userName);//這裏輸出什麼?
        }
        userName();
    }
    t('toby');
</script>

結果:

  • toby
  • function () {
    console.log(userName);//這裏輸出什麼?
    }

t(‘toby’)的分析和執行階段

分析階段:

創建AO對象,t.AO = {}

分析參數: t.AO = {userName : toby}

分析var聲明: 有同名屬性,不做任何事,還是t.AO = {userName : toby}

分析函數聲明: 無,略過

執行階段:

執行console.log(userName);時調用t.AO.userName 輸出爲toby,執行完後,代碼繼續往下執行,那麼就到了進行var的賦值操作(var的分析和執行的區別看例子2中我有解釋),此時t.AO = {userName : function userName() {console.log(userName);}}},代碼繼續往下執行,接着就執行到了userName()

userName()的分析和執行階段

分析階段:

創建AO對象,userName.AO = {}

分析參數: 無,略過

分析var聲明: 無,略過

分析函數聲明: 無,略過

執行階段:

按照例子4我們知道userName.AO是個空對象,所以會往上調用t.AO.userName,所以輸出爲:function userName() {console.log(userName);}}}

總結:
  後面的變量與前面的變量同名,如果後面變量的值是undefined,則不會覆蓋前面的,也就是隻有執行到了才賦值,賦了值才能覆蓋。也就是說,用這個同名變量時,看之前誰最後一次賦的值,就是那個值。(賦值爲undefined也算賦值)

var x=1;
console.log(x);//1
var x;
console.log(x);//1(這樣未賦值的undefine就覆蓋不了之前的,如果這裏給x=undefined,可以覆蓋,輸出結果就爲undefined)
var x=3;
console.log(x);//3

  JavaScript作用域會先在自己的AO上找,找不到就到父函數的AO上找,再找不到再找上一層的AO,直到找到window.這樣就形成一條鏈,這條AO鏈就是JavaScript中的作用域鏈.JavaScript中有兩條很重要的鏈,一條是作用域鏈,一條是原型鏈。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章