從零開始講解JavaScript中做用域鏈的概念及用途

從零開始講解JavaScript中做用域鏈的概念及用途

引言

先點贊,再看博客,順手能夠點個關注。
微信公衆號搜索【Lpyexplore的編程小屋】,關注我,帶你在python爬蟲的過程當中學習前端javascript

以前我寫過一篇關於JavaScript中的對象的一篇文章,裏面也提到了做用域鏈的概念,相信你們對這個概念仍是沒有很深的理解,而且這個概念也是面試中常常問到的,由於這個概念實在過重要了,在咱們平時寫代碼時,也可能會由於做用域鏈的問題,而出現莫名其妙的bug,致使咱們花費大量的時間都查找不出緣由。因此我就準備單獨寫一篇關於做用域鏈的文章,來幫你們更好地理解這個概念。html

正文

1、執行環境

首先,咱們要引入一個概念,叫作執行環境(下面簡稱環境)。在一個執行環境中,有一個與之關聯的變量對象(下面簡稱對象),在該對象中,儲存着這個執行環境中定義的變量和函數。但這個對象只是個形式上的對象,並不能被外界所訪問到。前端

例如,在瀏覽器中,咱們在全局運行下列代碼,那麼當前的執行環境就是window,也就是全局,而且與該全局環境關聯的對象中存儲着定義的變量fruitjava

<!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中,函數也會造成一個環境,例以下列的代碼中,函數的內部就是一個局部的環境,與該環境關聯的對象中存儲着變量my_favorite;而此時全局環境window中,也有一個與之關聯的對象,該對象中存儲着變量fruit 和函數 fn1python

<!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>

2、做用域鏈

看了上面兩個例子,咱們對執行環境應該有了必定的瞭解,那麼這裏就將引入做用域鏈的概念了,當代碼執行在一個環境中時,會針對環境中儲存變量和函數的對象建立一個做用域鏈,做用域鏈的最前端就是當前環境的對象,若是當前環境是個函數,則做用域鏈的下一部分就是全局的window環境的變量對象。web

先來看一下代碼部分面試

<script>
	let fruit = 'banana'
	function fn() {
		let color = 'red'
		//返回 '我最喜歡的水果是banana,我最喜歡的顏色是red'
		console.log('我最喜歡的水果是' + fruit + ',我最喜歡的顏色是' + color)
	}
	fn()
	//報錯, color is undefined
	console.log('我最喜歡的水果是' + fruit + ',我最喜歡的顏色是' + color)
</script>

首先執行了函數 fn ,此時函數內的做用域鏈就是這樣的編程

在這裏插入圖片描述
咱們看到,在函數 fn 中,咱們使用了變量 fruitcolor,因此此時會從做用域鏈的頭部開始,從第一個活動變量(本例中第一個變量對象就是函數fn的活動變量)中,尋找變量 fruitcolor,發現該變量對象中存在變量color,因而就成功引用了變量color,可是由於沒有找到變量 fruit,因而再沿着做用域鏈往下找到下一個變量對象(本例中第二個活動變量就是全局window的變量對象),發現該變量對象中有咱們想要的變量 fruit,則引用該變量 fruit ,同時,由於找到了須要引用的變量,就不會繼續沿着做用域鏈繼續向下尋找了。瀏覽器

咱們再來看在函數外,也就是全局window中,也執行了console.log('我最喜歡的水果是' + fruit + ',我最喜歡的顏色是' + color),此時在全局環境中的做用域鏈是這樣的
在這裏插入圖片描述
此時也使用了變量 fruitcolor,因此這時會從做用域鏈的頭部開始,找到第一個變量對象(本例中第一個活動變量就是window全局變量對象),發現該變量對象中有變量 fruit,因此成功引用該變量對象中的 fruit,但由於沒找到變量 color,因此繼續沿着做用域鏈向下尋找下一個活動變量,但此時已經找到了做用域鏈的尾部,並無別的變量對象了,因此也就沒法找到變量 color了,因此最後返回的就是 undefined。在本例中咱們能夠看到,當代碼處於全局環境中時,是沒有訪問函數fn執行環境中的變量color的權力的,這裏咱們能夠這種現象當作是變量color做用域只是在函數fn的執行環境內。微信

這就是代碼執行時,做用域鏈起到的做用,因此做用域鏈就保證了執行環境中代碼對變量的有序訪問。

3、塊級做用域

在JavaScript中是沒有塊級做用域的,也就是說,由花括號或小括號封閉起來的區域內沒有本身的做用域,例如這兩個例子

if(true) {
	var fruit = 'banana'
}
console.log(fruit)    //返回 banana

咱們能夠看到,if 語句中的花括號內,使用 var 定義了一個變量 fruit,按照做用域鏈的規則來講,外部是沒法訪問到該變量的,可是咱們能夠看到,確實返回了這個變量的值 banana

再來看下一個例子

for(var i=0; i<4; i++) {
	alert(i)
}
console.log(i)    //返回4

在使用 for語句時,咱們在小括號裏使用var定義了一個臨時變量i,一樣的的,在 for循環結束之後,在外部訪問該變量,也成功返回了相應的值。

以上兩個例子,都是由於JavaScript沒有塊級做用域引發的,因此有時會由於這種狀況,致使一些沒必要要的麻煩。在ES6中,出現了使用 letconst聲明變量的方式,來解決了JavaScript中沒有塊級做用域的問題。
大家能夠看我以前寫的一篇關於letconst 聲明變量的文章——尚未理解let 和 const的用法和區別嗎,幾百字讓你立馬搞懂

4、其餘狀況

其實,還有一種狀況,會影響變量的訪問順序,那就是在聲明變量時,直接給一個未聲明的變量賦值,例如這樣

function fn() {
	sum = 1 + 2
}
fn()

console.log(sum)      //返回 3

按照咱們本文前面講解的做用域鏈的知識,當執行到最後一局代碼時,此時處於全局執行環境中,查詢不到變量 sum,因此應當會報錯 undefined,但這裏卻返回了 3。

這是由於,在咱們使用var聲明變量時,會自動將該變量放到離該代碼最近的活動變量中去,也就是函數fn的活動變量中,因此在全局執行環境中的代碼就沒法訪問到該變量。可是若是不使用var,而是像這個例子中同樣,直接給一個未定義的變量賦值,這時會自動地將該變量放到全局的活動變量中去,這就是致使本例中在全局環境中還能訪問到變量sum的緣由。

5、總結

  1. 做用域鏈能夠當作是將變量對象按順序鏈接起來的一根鏈子
  2. 每一個執行環境中的做用域鏈都是不一樣的
  3. 當咱們引用變量時,會順着當前執行環境的做用域鏈,從做用域鏈的開頭開始依次往下尋找對應的變量,直到找到做用域鏈的尾部,報錯undefined
  4. 做用域鏈保證了變量的有序訪問

結束語

好了,對於做用域鏈的講解就到這裏了,相信這下你們對JavaScript中的做用域鏈有了很深的理解了吧,我相信,理解了這個概念,能夠消除咱們代碼中大部分不必的BUG。

我是前端Lpyexplore,原創不易,喜歡個人文章的點個關注,甩個贊,不嫌麻煩的評論支持一下,謝謝你們啦~

相關文章
相關標籤/搜索