作用域與作用域鏈

一巫员、作用域

一個變量的作用域(scope)是程序源代碼中定義的這個變量的區(qū)域又活。

1. 在JS中使用的是詞法作用域(lexical scope)

不在任何函數(shù)內(nèi)聲明的變量(函數(shù)內(nèi)省略var的也算全局)稱作全局變量(global scope)
在函數(shù)內(nèi)聲明的變量具有函數(shù)作用域(function scope)酸员,屬于局部變量

局部變量優(yōu)先級高于全局變量
var name="one";
function test(){
var name="two";
console.log(name);
//two
}
test();
函數(shù)內(nèi)省略var的奏篙,會影響全局變量尤慰,因為它實際上已經(jīng)被重寫成了全局變量
var name="one";
function test(){
name="two";
}
test();
console.log(name);//two
函數(shù)作用域晃择,就是說函數(shù)是一個作用域的基本單位,js不像c/c++那樣具有塊級作用域 比如 if for 等
function test(){
for(var i=0;i<10;i++){
if(i==5){
var name ="one";
}
}
console.log(name);//one
}
test();//因為是函數(shù)級作用域姆怪,所以可以訪問到name="one"

當然了叛赚,js里邊還使用到了高階函數(shù),其實可以理解成嵌套函數(shù)
functiontest1(){
var name ="one";
return function(){
console.log(name);
}
}
test1()();
test1()之后將調(diào)用外層函數(shù)稽揭,返回了一個內(nèi)層函數(shù)俺附,再繼續(xù)(),就相應調(diào)用執(zhí)行了內(nèi)層函數(shù)溪掀,所以就輸出 ”one"
嵌套函數(shù)涉及到了閉包事镣,后面再談..這里內(nèi)層函數(shù)可以訪問到外層函數(shù)中聲明的變量name,這就涉及到了作用域鏈機制

2. JS中的聲明提前

js中的函數(shù)作用域是指在函數(shù)內(nèi)聲明的所有變量在函數(shù)體內(nèi)始終是可見的揪胃。并且璃哟,變量在聲明之前就可以使用了,這種情況就叫做聲明提前(hoisting)
tip:聲明提前是在js引擎預編譯時就進行了只嚣,在代碼被執(zhí)行之前已經(jīng)有聲明提前的現(xiàn)象產(chǎn)生了
比如
var name="one";
function test(){
console.log(name);//undefined
var name="two";
console.log(name);//two
}
test();
上邊就達到了下面的效果
var name="one";
function test(){
var name;
console.log(name);//undefined
name="two";
console.log(name);//two
}
test();
再試試把var去掉沮稚?這是函數(shù)內(nèi)的name已經(jīng)變成了全局變量,所以不再是undefined
var name="one";
function test(){
console.log(name);//one
name="two";
console.log(name);//two
}
test();

3. 值得注意的是册舞,上面提到的都沒有傳參數(shù)蕴掏,如果test有參數(shù),又如何呢调鲸?

console.log(name);//one
name="two";
console.log(name);//two
}
var name ="one";
test(name);
console.log(name);// one
之前說過盛杰,基本類型是按值傳遞的,所以傳進test里面的name實際上只是一個副本藐石,函數(shù)返回之后這個副本就被清除了即供。
千萬不要以為函數(shù)里邊的name="two"把全局name修改了,因為它們是兩個獨立的name
(2)作用域鏈
上面提到的高級函數(shù)就涉及到了作用域鏈
function test1(){
var name ="one";
return function(){
console.log(name);
}
}
test1()();

1. 引入一大段話來解釋:

每一段js代碼(全局代碼或函數(shù))都有一個與之關聯(lián)的作用域鏈(scope chain)于微。
這個作用域鏈是一個對象列表或者鏈表逗嫡,這組對象定義了這段代碼中“作用域中”的變量青自。
當js需要查找變量x的值的時候(這個過程稱為變量解析(variable
resolution)),它會從鏈的第一個對象開始查找驱证,如果這個對象有一個名為x的屬性延窜,則會直接使用這個屬性的值,如果第一個對象中沒有名為x的屬性抹锄,js會繼續(xù)查找鏈上的下一個對象逆瑞。如果第二個對象依然沒有名為x的屬性,則會繼續(xù)查找下一個伙单,以此類推获高。如果作用域鏈上沒有任何一個對象含有屬性x,那么就認為這段代碼的作用域鏈上不存在x,并最終拋出一個引用錯誤(ReferenceError)異常。

2. 作用域鏈舉例:

在js最頂層代碼中(也就是不包括任何函數(shù)定義內(nèi)的代碼),作用域鏈由一個全局對象組成。
在不包含嵌套的函數(shù)體內(nèi)旧找,作用域鏈上有兩個對象,第一個是定義函數(shù)參數(shù)和局部變量的對象,第二個是全局對象域慷。
在一個嵌套的函數(shù)體內(nèi),作用域上至少有三個對象总寻。

3. 作用域鏈創(chuàng)建規(guī)則:

當定義一個函數(shù)時(注意器罐,是定義的時候就開始了),它實際上保存一個作用域鏈渐行。

當調(diào)用這個函數(shù)時轰坊,它創(chuàng)建一個新的對象來儲存它的參數(shù)或局部變量,并將這個對象添加保存至那個作用域鏈上祟印,同時創(chuàng)建一個新的更長的表示函數(shù)調(diào)用作用域的“鏈”肴沫。

對于嵌套函數(shù)來說,情況又有所變化:每次調(diào)用外部函數(shù)的時候蕴忆,內(nèi)部函數(shù)又會重新定義一遍颤芬。因為每次調(diào)用外部函數(shù)的時候,作用域鏈都是不同的套鹅。內(nèi)部函數(shù)在每次定義的時候都要微妙的差別---在每次調(diào)用外部函數(shù)時站蝠,內(nèi)部函數(shù)的代碼都是相同的,而且關聯(lián)這段代碼的作用域鏈也不相同卓鹿。

(tip: 把上面三點理解好菱魔,記住了,最好還要能用自己的話說出來吟孙,不然就背下來澜倦,因為面試官就直接問你:請描述一下作用域鏈...)

舉個作用域鏈的實用例子:
var name="one";
function test(){
var name="two";
function test1(){
var name="three";
console.log(name);//three
}
function test2(){
console.log(name);// two
}
test1();
test2();
}
test();
上邊是個嵌套函數(shù)聚蝶,相應的應該是作用域鏈上有三個對象
那么在調(diào)用的時候,需要查找name的值藻治,就在作用域鏈上查找

當成功調(diào)用test1()的時候碘勉,順序為 test1()->test()->全局對象window 因為在test1()上就找到了name的值three,所以完成搜索返回
當成功調(diào)用test1()的時候栋艳,順序為 test2()->test()->全局對象window 因為在test2()上沒找到name的值恰聘,所以找test()中的,找到了name的值two,就完成搜索返回

還有一個例子有時候我們會犯錯的吸占,面試的時候也經(jīng)常被騙到晴叨。
<html xmlns="http://www.w3.org/1999/xhtml">
<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); //都是 Button4
},false);
}
}
window.onload=buttonInit;
</script>
</head>
<body>
<button id="button1">Button1</button>
<button id="button2">Button2</button>
<button id="button3">Button3</button>
</body>
</html>
為什么?
根據(jù)作用域鏈中變量的尋找規(guī)則:
b.addEventListener("click",function(){
alert("Button"+i);
},false);
這里有一個函數(shù)矾屯,它是匿名函數(shù)兼蕊,既然是函數(shù),那就在作用域鏈上具有一個對象件蚕,這個函數(shù)里邊使用到了變量i孙技,它自然會在作用域上尋找它。
查找順序是 這個匿名函數(shù) -->外部的函數(shù)buttonInit() -->全局對象window

匿名函數(shù)中找不到i,自然跑到了buttonInit(), ok,在for中找到了排作,

這時注冊事件已經(jīng)結(jié)束了牵啦,不要以為它會一個一個把i放下來,因為函數(shù)作用域之內(nèi)的變量對作用域內(nèi)是一直可見的妄痪,就是說會保持到最后的狀態(tài)

當匿名函數(shù)要使用i的時候哈雏,注冊事件完了,i已經(jīng)變成了4衫生,所以都是Button4

那怎么解決呢裳瘪?

給它傳值進去吧,每次循環(huán)時罪针,再使用一個匿名函數(shù)彭羹,把for里邊的i傳進去,匿名函數(shù)的規(guī)則如代碼

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script type="text/javascript">
function buttonInit(){
for(var i=1;i<4;i++){
(function(data_i){
var b=document.getElementById("button"+data_i);
b.addEventListener("click",function(){
alert("Button"+data_i);
},false);

           })(i);
   }

}
window.onload=buttonInit;
</script>
</head>
<body>
<button id="button1">Button1</button>
<button id="button2">Button2</button>
<button id="button3">Button3</button>
</body>
</html>
這樣就可以 Button1..2..3了

4.上述就是作用域鏈的基本描述泪酱,另外派殷,with語句可用于臨時拓展作用域鏈(不推薦使用with)

語法形如:
with(object)
statement
這個with語句,將object添加到作用域鏈的頭部墓阀,然后執(zhí)行statement,最后把作用域鏈恢復到原始狀態(tài)
簡單用法:
比如給表單中各個項的值value賦值
一般可以我們直接這樣
var f = document.forms[0];
f.name.value ="";
f.age.value ="";
f.email.value ="";
引入with后(因為使用with會產(chǎn)生一系列問題愈腾,所以還是使用上面那張形式吧)
with
(document.forms[0]){
f.name.value ="";
f.age.value ="";
f.email.value ="";
}

另外,假如 一個對象o具有x屬性岂津,o.x = 1;
那么使用
with(o){
x = 2;
}
就可以轉(zhuǎn)換成 o.x = 2;
假如o沒有定義屬性x虱黄,它的功能就只是相當于 x = 2; 一個全局變量罷了。
因為with提供了一種讀取o的屬性的快捷方式吮成,但他并不能創(chuàng)建o本身沒有的屬性橱乱。
要理解變量的作用域范圍就得先理解作用域鏈
用var關鍵字聲明一個變量時辜梳,就是為該變量所在的對象添加了一個屬性。
作用域鏈:由于js的變量都是對象的屬性泳叠,而該對象可能又是其它對象的屬性作瞄,而所有的對象都是window對象的屬性,所以這些對象的關系可以看作是一條鏈
鏈頭就是變量所處的對象危纫,鏈尾就是window對象

看下面的代碼:
復制代碼 代碼如下:

function t() {
var a;
function t2() {
var b;
}
}

js中函數(shù)也是對象宗挥,所以變量a所在的對象是t,t又在window對象中种蝶,所以a的作用域鏈如下
t--window
那么b所以在的對象即t2契耿,t2又包含在t中,t又在window對象螃征,所以b的作用域鏈如下
t2--t--window
明白了作用域鏈下面就開始變量的作用域分析了
1 javascript 沒有var的變量都為全局變量搪桂,且為window對象的屬性
復制代碼 代碼如下:

function test1() {
//執(zhí)行這個句的時候它會找作用域?qū)ο螅@個函數(shù)就是作用域鏈中的第一個對象盯滚,但這個對象中沒有相關的var語句
//于里就找作用域鏈的第二個對象踢械,即全局對象,而全局對象中也沒有相關的var語句
//由于沒有相關的var語句魄藕,js隱式在函數(shù)地聲明了變量即var all;
all = 30;
alert(all);
}
test1();
alert(all);
alert(window.all);

2 函數(shù)內(nèi)(函數(shù)內(nèi)的函數(shù)除外)定義的變量在整個函數(shù)內(nèi)部都有效
復制代碼 代碼如下:

function test2() {
var t = 0;
//在for的條件里定義變量内列,這個變更的作用域鏈對象是這個函數(shù)
//因此在整個的函數(shù)里它是有效的
for (var i = 0; i < 5; i++) {
t += i;
}
alert(i);
}
test2();

3 函數(shù)內(nèi)部的變量取代全局同名變量
復制代碼 代碼如下:

var t = "bb";
function test() {
//執(zhí)行t的時候,它會先找作用域鏈對象背率,由于它定義在函數(shù)內(nèi)部德绿,所以這個函數(shù)就是它的作用域鏈的第一個對象
//而在這個對象里又有t的定義,所以t就是局部變量了退渗,它替換了全局變量t
//t只是此時有定義,但并沒有賦值蕴纳,賦值在下一行会油,所以這里輸出了undefined
alert(t);
var t = "aa";
alert(t);
}
test();

4 沒塊的作用域
復制代碼 代碼如下:

if (true) {
//在塊中定義了一個變量,它的作用域鏈的第一個對象就是全局對象window
var tmp = 0;
}
//tmp的作用域鏈的第一個對象就是全局對象window古毛,而上面又有全局對象中相關的var語句翻翩,因此輸出0
alert(tmp);

以下內(nèi)容來自讀網(wǎng)上博客的總結(jié),當筆記使用稻薇,只記重點嫂冻,同時非常感謝樂于分享的博主們,是你們讓我站在了巨人的肩旁上塞椎!
1桨仿、
復制代碼 代碼如下:

var temp = (function(){
var name ="test";
return function(){
alert(name);
}
})();

以上代碼片斷是我們jser經(jīng)常見到的寫法,是傳說中的閉包案狠。 眾所周知:調(diào)用 temp();會彈出 “ test”;該過程可以有以下三條理論作為依據(jù)來解釋:

1)js 作用域只和函數(shù)的界定符相關服傍,函數(shù)與函數(shù)的嵌套形成了作用域鏈钱雷;
2)作用域鏈的創(chuàng)建規(guī)則是復制上一層環(huán)境的作用域鏈,并將指向本環(huán)境變量對象的指針放到鏈首吹零;
3)在Javascript中罩抗,如果一個對象不再被引用,那么這個對象就會被GC回收灿椅。如果兩個對象互相引用套蒂,而不再被第3者所引用,那么這兩個互相引用的對象也會被回收茫蛹。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末操刀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子麻惶,更是在濱河造成了極大的恐慌馍刮,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件窃蹋,死亡現(xiàn)場離奇詭異卡啰,居然都是意外死亡,警方通過查閱死者的電腦和手機警没,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進店門匈辱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人杀迹,你說我怎么就攤上這事亡脸。” “怎么了树酪?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵浅碾,是天一觀的道長。 經(jīng)常有香客問我续语,道長垂谢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任疮茄,我火速辦了婚禮滥朱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘力试。我一直安慰自己徙邻,他們只是感情好,可當我...
    茶點故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布畸裳。 她就那樣靜靜地躺著缰犁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上民鼓,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天薇芝,我揣著相機與錄音,去河邊找鬼丰嘉。 笑死夯到,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的饮亏。 我是一名探鬼主播耍贾,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼路幸!你這毒婦竟也來了荐开?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤简肴,失蹤者是張志新(化名)和其女友劉穎晃听,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體砰识,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡能扒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了辫狼。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片初斑。...
    茶點故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖膨处,靈堂內(nèi)的尸體忽然破棺而出见秤,到底是詐尸還是另有隱情,我是刑警寧澤真椿,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布鹃答,位于F島的核電站,受9級特大地震影響突硝,放射性物質(zhì)發(fā)生泄漏测摔。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一狞换、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧舟肉,春花似錦修噪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春脏款,著一層夾襖步出監(jiān)牢的瞬間围苫,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工撤师, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留剂府,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓剃盾,卻偏偏與公主長得像腺占,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子痒谴,可洞房花燭夜當晚...
    茶點故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容