1. 程式人生 > >Js作用域與作用域鏈詳解

Js作用域與作用域鏈詳解

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

一:函式作用域

   先看一小段程式碼:

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"


二:變數作用域

還是首先看一段程式碼:

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    ->可刪除

三:作用域鏈

先來看一段程式碼:

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.onload=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“。

四: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語句結束後,作用域鏈恢復正常。