JavaScript中作用域鏈的概念及用途講解
從零開始講解JavaScript中作用域鏈的概念及用途
引言
之前我寫過一篇關(guān)于JavaScript中的對(duì)象的一篇文章,里面也提到了作用域鏈的概念,相信大家對(duì)這個(gè)概念還是沒有很深的理解,并且這個(gè)概念也是面試中經(jīng)常問到的,因?yàn)檫@個(gè)概念實(shí)在太重要了,在我們平時(shí)寫代碼時(shí),也可能會(huì)因?yàn)樽饔糜蜴湹膯栴},而出現(xiàn)莫名其妙的bug,導(dǎo)致我們花費(fèi)大量的時(shí)間都查找不出原因。所以我就準(zhǔn)備單獨(dú)寫一篇關(guān)于作用域鏈的文章,來幫大家更好地理解這個(gè)概念。
正文
一、執(zhí)行環(huán)境
首先,我們要引入一個(gè)概念,叫做執(zhí)行環(huán)境(下面簡(jiǎn)稱環(huán)境)。在一個(gè)執(zhí)行環(huán)境中,有一個(gè)與之關(guān)聯(lián)的變量對(duì)象(下面簡(jiǎn)稱對(duì)象),在該對(duì)象中,儲(chǔ)存著這個(gè)執(zhí)行環(huán)境中定義的變量和函數(shù)。但這個(gè)對(duì)象只是個(gè)形式上的對(duì)象,并不能被外界所訪問到。
例如,在瀏覽器中,我們?cè)谌诌\(yùn)行下列代碼,那么當(dāng)前的執(zhí)行環(huán)境就是window,也就是全局,并且與該全局環(huán)境關(guān)聯(lián)的對(duì)象中存儲(chǔ)著定義的變量fruit
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> let fruit = 'banana' alert(fruit) </script> </body> </html>
那么,在javascript中,函數(shù)也會(huì)形成一個(gè)環(huán)境,例如下列的代碼中,函數(shù)的內(nèi)部就是一個(gè)局部的環(huán)境,與該環(huán)境關(guān)聯(lián)的對(duì)象中存儲(chǔ)著變量my_favorite;而此時(shí)全局環(huán)境window中,也有一個(gè)與之關(guān)聯(lián)的對(duì)象,該對(duì)象中存儲(chǔ)著變量fruit 和函數(shù) fn1
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> let fruit = 'banana' alert(fruit) function fn1() { let my_favorite = 'apple' return my_favorite } fn1() </script> </body> </html>
二、作用域鏈
看了上面兩個(gè)例子,我們對(duì)執(zhí)行環(huán)境應(yīng)該有了一定的了解,那么這里就將引入作用域鏈的概念了,當(dāng)代碼執(zhí)行在一個(gè)環(huán)境中時(shí),會(huì)針對(duì)環(huán)境中儲(chǔ)存變量和函數(shù)的對(duì)象創(chuàng)建一個(gè)作用域鏈,作用域鏈的最前端就是當(dāng)前環(huán)境的對(duì)象,如果當(dāng)前環(huán)境是個(gè)函數(shù),則作用域鏈的下一部分就是全局的window環(huán)境的變量對(duì)象。
先來看一下代碼部分
<script> let fruit = 'banana' function fn() { let color = 'red' //返回 '我最喜歡的水果是banana,我最喜歡的顏色是red' console.log('我最喜歡的水果是' + fruit + ',我最喜歡的顏色是' + color) } fn() //報(bào)錯(cuò), color is undefined console.log('我最喜歡的水果是' + fruit + ',我最喜歡的顏色是' + color) </script>
首先執(zhí)行了函數(shù) fn ,此時(shí)函數(shù)內(nèi)的作用域鏈就是這樣的
我們看到,在函數(shù) fn 中,我們使用了變量 fruit 和 color,所以此時(shí)會(huì)從作用域鏈的頭部開始,從第一個(gè)活動(dòng)變量(本例中第一個(gè)變量對(duì)象就是函數(shù)fn的活動(dòng)變量)中,尋找變量 fruit和 color,發(fā)現(xiàn)該變量對(duì)象中存在變量color,于是就成功引用了變量color,但是因?yàn)闆]有找到變量 fruit,于是再沿著作用域鏈往下找到下一個(gè)變量對(duì)象(本例中第二個(gè)活動(dòng)變量就是全局window的變量對(duì)象),發(fā)現(xiàn)該變量對(duì)象中有我們想要的變量 fruit,則引用該變量 fruit ,同時(shí),因?yàn)檎业搅诵枰玫淖兞?,就不?huì)繼續(xù)沿著作用域鏈繼續(xù)向下尋找了。
我們?cè)賮砜丛诤瘮?shù)外,也就是全局window中,也執(zhí)行了console.log('我最喜歡的水果是' + fruit + ',我最喜歡的顏色是' + color),此時(shí)在全局環(huán)境中的作用域鏈?zhǔn)沁@樣的
此時(shí)也使用了變量 fruit 和 color,所以這時(shí)會(huì)從作用域鏈的頭部開始,找到第一個(gè)變量對(duì)象(本例中第一個(gè)活動(dòng)變量就是window全局變量對(duì)象),發(fā)現(xiàn)該變量對(duì)象中有變量 fruit,所以成功引用該變量對(duì)象中的 fruit,但因?yàn)闆]找到變量 color,所以繼續(xù)沿著作用域鏈向下尋找下一個(gè)活動(dòng)變量,但此時(shí)已經(jīng)找到了作用域鏈的尾部,并沒有別的變量對(duì)象了,所以也就無法找到變量 color了,所以最后返回的就是 undefined。在本例中我們可以看到,當(dāng)代碼處于全局環(huán)境中時(shí),是沒有訪問函數(shù)fn執(zhí)行環(huán)境中的變量color的權(quán)力的,這里我們可以這種現(xiàn)象看成是變量color的作用域只是在函數(shù)fn的執(zhí)行環(huán)境內(nèi)。
這就是代碼執(zhí)行時(shí),作用域鏈起到的作用,所以作用域鏈就保證了執(zhí)行環(huán)境中代碼對(duì)變量的有序訪問。
三、塊級(jí)作用域
在JavaScript中是沒有塊級(jí)作用域的,也就是說,由花括號(hào)或小括號(hào)封閉起來的區(qū)域內(nèi)沒有自己的作用域,例如這兩個(gè)例子
if(true) { var fruit = 'banana' } console.log(fruit) //返回 banana
我們可以看到,if 語句中的花括號(hào)內(nèi),使用 var 定義了一個(gè)變量 fruit,按照作用域鏈的規(guī)則來說,外部是無法訪問到該變量的,但是我們可以看到,確實(shí)返回了這個(gè)變量的值 banana 。
再來看下一個(gè)例子
for(var i=0; i<4; i++) { alert(i) } console.log(i) //返回4
在使用 for語句時(shí),我們?cè)谛±ㄌ?hào)里使用var定義了一個(gè)臨時(shí)變量i,同樣的的,在 for循環(huán)結(jié)束以后,在外部訪問該變量,也成功返回了相應(yīng)的值。
以上兩個(gè)例子,都是因?yàn)镴avaScript沒有塊級(jí)作用域引起的,所以有時(shí)會(huì)因?yàn)檫@種情況,導(dǎo)致一些不必要的麻煩。在ES6中,出現(xiàn)了使用 let 和 const聲明變量的方式,來解決了JavaScript中沒有塊級(jí)作用域的問題。
你們可以看我之前寫的一篇關(guān)于let 和 const 聲明變量的文章——還沒有理解let 和 const的用法和區(qū)別嗎,幾百字讓你立馬搞懂
四、其他情況
其實(shí),還有一種情況,會(huì)影響變量的訪問順序,那就是在聲明變量時(shí),直接給一個(gè)未聲明的變量賦值,例如這樣
function fn() { sum = 1 + 2 } fn() console.log(sum) //返回 3
按照我們本文前面講解的作用域鏈的知識(shí),當(dāng)執(zhí)行到最后一局代碼時(shí),此時(shí)處于全局執(zhí)行環(huán)境中,查詢不到變量 sum,所以應(yīng)當(dāng)會(huì)報(bào)錯(cuò) undefined,但這里卻返回了 3。
這是因?yàn)椋谖覀兪褂胿ar聲明變量時(shí),會(huì)自動(dòng)將該變量放到離該代碼最近的活動(dòng)變量中去,也就是函數(shù)fn的活動(dòng)變量中,所以在全局執(zhí)行環(huán)境中的代碼就無法訪問到該變量。但是如果不使用var,而是像這個(gè)例子中一樣,直接給一個(gè)未定義的變量賦值,這時(shí)會(huì)自動(dòng)地將該變量放到全局的活動(dòng)變量中去,這就是導(dǎo)致本例中在全局環(huán)境中還能訪問到變量sum的原因。
五、總結(jié)
- 作用域鏈可以看成是將變量對(duì)象按順序連接起來的一根鏈子
- 每個(gè)執(zhí)行環(huán)境中的作用域鏈都是不同的
- 當(dāng)我們引用變量時(shí),會(huì)順著當(dāng)前執(zhí)行環(huán)境的作用域鏈,從作用域鏈的開頭開始依次往下尋找對(duì)應(yīng)的變量,直到找到作用域鏈的尾部,報(bào)錯(cuò)undefined
- 作用域鏈保證了變量的有序訪問
結(jié)束語
好了,對(duì)于作用域鏈的講解就到這里了,相信這下大家對(duì)JavaScript中的作用域鏈有了很深的理解了吧,我相信,理解了這個(gè)概念,可以消除我們代碼中大部分沒必要的BUG。
到此這篇關(guān)于JavaScript中作用域鏈的概念及用途講解的文章就介紹到這了,更多相關(guān)JavaScript中作用域鏈內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
利用JavaScript控制元素(標(biāo)簽)的顯示與隱藏
這篇文章主要給大家介紹了關(guān)于如何利用JavaScript控制元素(標(biāo)簽)的顯示與隱藏的相關(guān)資料,JavaScript有多種方式可以實(shí)現(xiàn),文中介紹了三種方法以及區(qū)別,需要的朋友可以參考下2023-07-07JS+CSS實(shí)現(xiàn)實(shí)用的單擊輸入框彈出選擇框的方法
這篇文章主要介紹了JS+CSS實(shí)現(xiàn)實(shí)用的單擊輸入框彈出選擇框的方法,實(shí)例分析了javascript操作select及button的操作技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下2015-02-02IE php關(guān)于強(qiáng)制下載文件的代碼
這個(gè)東東,把我搞得暈暈乎乎的,F(xiàn)F下,沒有強(qiáng)制下載文件這個(gè)問題。2008-08-08解決js頁面滾動(dòng)效果scrollTop在FireFox與Chrome瀏覽器間的兼容問題的方法
這篇文章為大家分享了解決js頁面滾動(dòng)效果scrollTop在FireFox與Chrome瀏覽器間的兼容問題的方法,感興趣或者是遇到這種問題的朋友可以參考這篇文章2015-12-12javascript中的void運(yùn)算符語法及使用介紹
void是javascript中的一個(gè)操作符,void會(huì)計(jì)算表達(dá)式的值,但是會(huì)丟棄表達(dá)式的返回值接下來將詳細(xì)介紹下,感興趣的你可以參考下或許對(duì)你有所幫助2013-03-03解決layui上傳文件提示上傳異常,實(shí)際文件已經(jīng)上傳成功的問題
今天小編就為大家分享一篇解決layui上傳文件提示上傳異常,實(shí)際文件已經(jīng)上傳成功的問題。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-08-08