【Js】深入學習作用域與作用域鏈

文章轉自http://blog.csdn.net/yueguanghaidao/article/details/9568071


一直對Js的作用域有點迷糊,今天偶然讀到Javascript權威指南,立馬被吸引住了,寫的真不錯。我看的是第六版本,相當的厚,大概1000多頁,Js博大精深,要熟悉精通需要大毅力大功夫。

1 函數作用域

先看一小段代碼:

var scope="global";
function t(){
    console.log(scope);
    var scope="local"
    console.log(scope);
}
t();

(PS: console.log()是firebug提供的調試工具,很好用,有興趣的童鞋可以用下,比瀏覽器+alert好用多了)
第一句輸出的是: “undefined”,而不是 “global”
第二講輸出的是:”local”

你可能會認爲第一句會輸出:”global”,因爲代碼還沒執行var scope=”local”,所以肯定會輸出“global”。
我說這想法完全沒錯,只不過用錯了對象。我們首先要區分Javascript的函數作用域與我們熟知的C/C++等的塊級作用域。
在C/C++中,花括號內中的每一段代碼都具有各自的作用域,而且變量在聲明它們的代碼段之外是不可見的。而Javascript壓根沒有塊級作用域,而是函數作用域.
所謂函數作用域就是說:-》變量在聲明它們的函數體以及這個函數體嵌套的任意函數體內都是有定義的。
所以根據函數作用域的意思,可以將上述代碼重寫如下:

var scope="global";
function t(){
    var scope;
    console.log(scope);
    scope="local"
    console.log(scope);
}
t();

我們可以看到,由於函數作用域的特性,局部變量在整個函數體始終是由定義的,我們可以將變量聲明”提前“到函數體頂部,同時變量初始化還在原來位置。
爲什麼說Js沒有塊級作用域呢,有以下代碼爲證:

var name="global";
if(true){
    var name="local";
    console.log(name)
}
console.log(name);

都輸出是“local”,如果有塊級作用域,明顯if語句將創建局部變量name,並不會修改全局name,可是沒有這樣,所以Js沒有塊級作用域。
現在很好理解爲什麼會得出那樣的結果了。scope聲明覆蓋了全局的scope,但是還沒有賦值,所以輸出:”undefined“。
所以下面的代碼也就很好理解了。

function t(flag){
    if(flag){
        var s="ifscope";
        for(var i=0;i<2;i++) 
            ;
    }
    console.log(i);
    console.log(s);
}
t(true);

輸出:2 ”ifscope”

2 變量作用域

還是首先看一段代碼:

function t(flag){
    if(flag){
        s="ifscope";
        for(var i=0;i<2;i++) 
            ;
    }
    console.log(i);
}
t(true);
console.log(s);

就是上面的翻版,知識將聲明s中的var去掉。
程序會報錯還是輸出“ifscope”呢?
讓我揭開謎底吧,會輸出:”ifscope”
這主要是Js中沒有用var聲明的變量都是全局變量,而且是頂層對象的屬性。
所以你用console.log(window.s)也是會輸出“ifconfig”

當使用var聲明一個變量時,創建的這個屬性是不可配置的,也就是說無法通過delete運算符刪除
var name=1 ->不可刪除
sex=”girl“ ->可刪除
this.age=22 ->可刪除

3 作用域鏈

先來看一段代碼:

name="lwy";
function t(){
    var name="tlwy";
    function s(){
        var name="slwy";
        console.log(name);
    }
    function ss(){
        console.log(name);
    }
    s();
    ss();
}
t();

當執行s時,將創建函數s的執行環境(調用對象),並將該對象置於鏈表開頭,然後將函數t的調用對象鏈接在之後,最後是全局對象。然後從鏈表開頭尋找變量name,很明顯
name是”slwy”。
但執行ss()時,作用域鏈是: ss()->t()->window,所以name是”tlwy”
下面看一個很容易犯錯的例子:

<html>
<head>
<script type="text/javascript">
function buttonInit(){
    for(var i=1;i<4;i++){
        var b=document.getElementById("button"+i);
        b.addEventListener("click",function(){ alert("Button"+i);},false);
    }
}
window.οnlοad=buttonInit;
</script>
</head>
<body>
<button id="button1">Button1</button>
<button id="button2">Button2</button>
<button id="button3">Button3</button>
</body>
</html>

當文檔加載完畢,給幾個按鈕註冊點擊事件,當我們點擊按鈕時,會彈出什麼提示框呢?
很容易犯錯,對是的,三個按鈕都是彈出:”Button4”,你答對了嗎?
當註冊事件結束後,i的值爲4,當點擊按鈕時,事件函數即function(){ alert(“Button”+i);}這個匿名函數中沒有i,根據作用域鏈,所以到buttonInit函數中找,此時i的值爲4,
所以彈出”button4“。

4 with語句

說到作用域鏈,不得不說with語句。with語句主要用來臨時擴展作用域鏈,將語句中的對象添加到作用域的頭部。
看下面代碼

person={name:"yhb",age:22,height:175,wife:{name:"lwy",age:21}};
with(person.wife){
    console.log(name);
}

with語句將person.wife添加到當前作用域鏈的頭部,所以輸出的就是:“lwy”.
with語句結束後,作用域鏈恢復正常。

發佈了72 篇原創文章 · 獲贊 12 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章