作用域
先來談?wù)勛兞康淖饔糜?br>
變量的作用域無非就是兩種:全局變量和局部變量。
全局作用域:
最外層函數(shù)定義的變量擁有全局作用域,即對任何內(nèi)部函數(shù)來說,都是可以訪問的:
<script>
var outerVar = "outer";
function fn(){
console.log(outerVar);
}
fn();//result:outer
</script>
局部作用域:
和全局作用域相反,局部作用域一般只在固定的代碼片段內(nèi)可訪問到,而對于函數(shù)外部是無法訪問的,最常見的例如函數(shù)內(nèi)部
<script>
function fn(){
var innerVar = "inner";
}
fn();
console.log(innerVar);// ReferenceError: innerVar is not defined
</script>
需要注意的是,函數(shù)內(nèi)部聲明變量的時候,一定要使用var命令。如果不用的話,你實際上聲明了一個全局變量!
<script>
function fn(){
innerVar = "inner";
}
fn();
console.log(innerVar);// result:inner
</script>
再來看一個代碼:
<script>
var scope = "global";
function fn(){
console.log(scope);//result:undefined
var scope = "local";
console.log(scope);//result:local;
}
fn();
</script>
很有趣吧,第一個輸出居然是undefined,原本以為它會訪問外部的全局變量(scope=”global”),但是并沒有。這可以算是javascript的一個特點,只要函數(shù)內(nèi)定義了一個局部變量,函數(shù)在解析的時候都會將這個變量“提前聲明”:
<script>
var scope = "global";
function fn(){
var scope;//提前聲明了局部變量
console.log(scope);//result:undefined
scope = "local";
console.log(scope);//result:local;
}
fn();
</script>
然而,也不能因此草率地將局部作用域定義為:用var聲明的變量作用范圍起止于花括號之間。
javascript并沒有塊級作用域
那什么是塊級作用域?
像在C/C++中,花括號內(nèi)中的每一段代碼都具有各自的作用域,而且變量在聲明它們的代碼段之外是不可見的,比如下面的c語言代碼:
for(int i = 0; i < 10; i++){
//i的作用范圍只在這個for循環(huán)
}
printf("%d",&i);//error
但是javascript不同,并沒有所謂的塊級作用域,javascript的作用域是相對函數(shù)而言的,可以稱為函數(shù)作用域:
<script>
for(var i = 1; i < 10; i++){
//coding
}
console.log(i); //10
</script>
作用域鏈(Scope Chain)
那什么是作用域鏈?
我的理解就是,根據(jù)在內(nèi)部函數(shù)可以訪問外部函數(shù)變量的這種機制,用鏈式查找決定哪些數(shù)據(jù)能被內(nèi)部函數(shù)訪問。
想要知道js怎么鏈式查找,就得先了解js的執(zhí)行環(huán)境
執(zhí)行環(huán)境(execution context)
每個函數(shù)運行時都會產(chǎn)生一個執(zhí)行環(huán)境,而這個執(zhí)行環(huán)境怎么表示呢?js為每一個執(zhí)行環(huán)境關(guān)聯(lián)了一個變量對象。環(huán)境中定義的所有變量和函數(shù)都保存在這個對象中。
全局執(zhí)行環(huán)境是最外圍的執(zhí)行環(huán)境,全局執(zhí)行環(huán)境被認為是window對象,因此所有的全局變量和函數(shù)都作為window對象的屬性和方法創(chuàng)建的。
js的執(zhí)行順序是根據(jù)函數(shù)的調(diào)用來決定的,當(dāng)一個函數(shù)被調(diào)用時,該函數(shù)環(huán)境的變量對象就被壓入一個環(huán)境棧中。而在函數(shù)執(zhí)行之后,棧將該函數(shù)的變量對象彈出,把控制權(quán)交給之前的執(zhí)行環(huán)境變量對象。
舉個例子:
<script>
var scope = "global";
function fn1(){
return scope;
}
function fn2(){
return scope;
}
fn1();
fn2();
</script>
上面代碼執(zhí)行情況演示:

了解了環(huán)境變量,再詳細講講作用域鏈。
當(dāng)某個函數(shù)第一次被調(diào)用時,就會創(chuàng)建一個執(zhí)行環(huán)境(execution context)以及相應(yīng)的作用域鏈,并把作用域鏈賦值給一個特殊的內(nèi)部屬性([scope])。然后使用this,arguments(arguments在全局環(huán)境中不存在)和其他命名參數(shù)的值來初始化函數(shù)的活動對象(activation object)。當(dāng)前執(zhí)行環(huán)境的變量對象始終在作用域鏈的第0位。
以上面的代碼為例,當(dāng)?shù)谝淮握{(diào)用fn1()時的作用域鏈如下圖所示:
(因為fn2()還沒有被調(diào)用,所以沒有fn2的執(zhí)行環(huán)境)

可以看到fn1活動對象里并沒有scope變量,于是沿著作用域鏈(scope chain)向后尋找,結(jié)果在全局變量對象里找到了scope,所以就返回全局變量對象里的scope值。
標(biāo)識符解析是沿著作用域鏈一級一級地搜索標(biāo)識符地過程。搜索過程始終從作用域鏈地前端開始,然后逐級向后回溯,直到找到標(biāo)識符為止(如果找不到標(biāo)識符,通常會導(dǎo)致錯誤發(fā)生)—-《JavaScript高級程序設(shè)計》
那作用域鏈地作用僅僅只是為了搜索標(biāo)識符嗎?
再來看一段代碼:
<script>
function outer(){
var scope = "outer";
function inner(){
return scope;
}
return inner;
}
var fn = outer();
fn();
</script>
outer()內(nèi)部返回了一個inner函數(shù),當(dāng)調(diào)用outer時,inner函數(shù)的作用域鏈就已經(jīng)被初始化了(復(fù)制父函數(shù)的作用域鏈,再在前端插入自己的活動對象),具體如下圖:

一般來說,當(dāng)某個環(huán)境中的所有代碼執(zhí)行完畢后,該環(huán)境被銷毀(彈出環(huán)境棧),保存在其中的所有變量和函數(shù)也隨之銷毀(全局執(zhí)行環(huán)境變量直到應(yīng)用程序退出,如網(wǎng)頁關(guān)閉才會被銷毀)
但是像上面那種有內(nèi)部函數(shù)的又有所不同,當(dāng)outer()函數(shù)執(zhí)行結(jié)束,執(zhí)行環(huán)境被銷毀,但是其關(guān)聯(lián)的活動對象并沒有隨之銷毀,而是一直存在于內(nèi)存中,因為該活動對象被其內(nèi)部函數(shù)的作用域鏈所引用。
具體如下圖:
outer執(zhí)行結(jié)束,內(nèi)部函數(shù)開始被調(diào)用
outer執(zhí)行環(huán)境等待被回收,outer的作用域鏈對全局變量對象和outer的活動對象引用都斷了

像上面這種內(nèi)部函數(shù)的作用域鏈仍然保持著對父函數(shù)活動對象的引用,就是閉包(closure)
閉包
閉包有兩個作用:
第一個就是可以讀取自身函數(shù)外部的變量(沿著作用域鏈尋找)
第二個就是讓這些外部變量始終保存在內(nèi)存中
關(guān)于第二點,來看一下以下的代碼:
<script>
function outer(){
var result = new Array();
for(var i = 0; i < 2; i++){//注:i是outer()的局部變量
result[i] = function(){
return i;
}
}
return result;//返回一個函數(shù)對象數(shù)組
//這個時候會初始化result.length個關(guān)于內(nèi)部函數(shù)的作用域鏈
}
var fn = outer();
console.log(fn[0]());//result:2
console.log(fn[1]());//result:2
</script>
返回結(jié)果很出乎意料吧,你肯定以為依次返回0,1,但事實并非如此
來看一下調(diào)用 fn[0]()的作用域鏈圖:

可以看到result[0]函數(shù)的活動對象里并沒有定義i這個變量,于是沿著作用域鏈去找i變量,結(jié)果在父函數(shù)outer的活動對象里找到變量i(值為2),而這個變量i是父函數(shù)執(zhí)行結(jié)束后將最終值保存在內(nèi)存里的結(jié)果。
由此也可以得出,js函數(shù)內(nèi)的變量值不是在編譯的時候就確定的,而是等在運行時期再去尋找的。
那怎么才能讓result數(shù)組函數(shù)返回我們所期望的值呢?
看一下result的活動對象里有一個arguments,arguments對象是一個參數(shù)的集合,是用來保存對象的。
那么我們就可以把i當(dāng)成參數(shù)傳進去,這樣一調(diào)用函數(shù)生成的活動對象內(nèi)的arguments就有當(dāng)前i的副本。
改進之后:
<script>
function outer(){
var result = new Array();
for(var i = 0; i < 2; i++){
//定義一個帶參函數(shù)
function arg(num){
return num;
}
//把i當(dāng)成參數(shù)傳進去
result[i] = arg(i);
}
return result;
}
var fn = outer();
console.log(fn[0]);//result:0
console.log(fn[1]);//result:1
</script>
雖然的到了期望的結(jié)果,但是又有人問這算閉包嗎?調(diào)用內(nèi)部函數(shù)的時候,父函數(shù)的環(huán)境變量還沒被銷毀呢,而且result返回的是一個整型數(shù)組,而不是一個函數(shù)數(shù)組!
確實如此,那就讓arg(num)函數(shù)內(nèi)部再定義一個內(nèi)部函數(shù)就好了:
這樣result返回的其實是innerarg()函數(shù)
<script>
function outer(){
var result = new Array();
for(var i = 0; i < 2; i++){
//定義一個帶參函數(shù)
function arg(num){
function innerarg(){
return num;
}
return innerarg;
}
//把i當(dāng)成參數(shù)傳進去
result[i] = arg(i);
}
return result;
}
var fn = outer();
console.log(fn[0]());
console.log(fn[1]());
</script>
當(dāng)調(diào)用outer,for循環(huán)內(nèi)i=0時的作用域鏈圖如下:

由上圖可知,當(dāng)調(diào)用innerarg()時,它會沿作用域鏈找到父函數(shù)arg()活動對象里的arguments參數(shù)num=0.
上面代碼中,函數(shù)arg在outer函數(shù)內(nèi)預(yù)先被調(diào)用執(zhí)行了,對于這種方法,js有一種簡潔的寫法
function outer(){
var result = new Array();
for(var i = 0; i < 2; i++){
//定義一個帶參函數(shù)
result[i] = function(num){
function innerarg(){
return num;
}
return innerarg;
}(i);//預(yù)先執(zhí)行函數(shù)寫法
//把i當(dāng)成參數(shù)傳進去
}
return result;
}
關(guān)于this對象
關(guān)于閉包經(jīng)常會看到這么一道題:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
alert(object.getNameFunc()());//result:The Window
《javascript高級程序設(shè)計》一書給出的解釋是:
this對象是在運行時基于函數(shù)的執(zhí)行環(huán)境綁定的:在全局函數(shù)中,this等于window,而當(dāng)函數(shù)被作為某個對象調(diào)用時,this等于那個對象。不過,匿名函數(shù)具有全局性,因此this對象同常指向window
作者:mayday526
來源:CSDN
原文:https://blog.csdn.net/whd526/article/details/70990994
版權(quán)聲明:本文為博主原創(chuàng)文章,轉(zhuǎn)載請附上博文鏈接!