作用域
JavaScript的作用域限定了你可以訪問哪些變量。有兩種作用域:全局作用域,局部作用域。
全局作用域
在所有函數聲明或者大括號之外定義的變量,都在全局作用域里。
不過這個規則只在瀏覽器中運行的JavaScript里有效。
可以在全局作用域定義變量,并不推薦這樣做。因為可能會引起命名沖突,兩個或更多的變量使用相同的變量名。在定義變量時使用了const或者let,那么在命名有沖突時,就會收到錯誤提示。這是不可取的。
所以,應該盡量使用局部變量,而不是全局變量
局部作用域
在代碼某一個具體范圍內使用的變量都可以在局部作用域內定義。這就是局部變量。
JavaScript里有兩種局部作用域:函數作用域和塊級作用域。
從函數作用域開始。
函數作用域
在函數里定義一個變量時,它在函數內任何地方都可以使用。在函數之外,就無法訪問它了。
比如下面這個例子,在sayHello函數內的hello變量:
塊級作用域是函數作用域的子集,因為函數是需要用大括號定義的,(除非你明確使用return語句和箭頭函數)。
函數提升和作用域
當使用function定義時,這個函數都會被提升到當前作用域的頂部。因此,下面的代碼是等效的:
因為這里有兩個變量,函數提升可能會導致混亂,因此就不會生效。所以一定要在使用函數之前定義函數。
函數不能訪問其他函數的作用域
在分別定義的不同的函數時,雖然可以在一個函數里調用一個函數,但一個函數依然不能訪問其他函數的作用域內部。
下面這例,second就不能訪問firstFunctionVariable這一變量。
嵌套作用域
如果在函數內部又定義了函數,那么內層函數可以訪問外層函數的變量,但反過來則不行。這樣的效果就是詞法作用域。
外層函數并不能訪問內部函數的變量。
如果把作用域的機制可視化,可以想象有一個雙向鏡(單面透視玻璃)。能從里面看到外面,但是外面的人不能看到你。
函數作用域就像是雙向鏡一樣。可以從里面向外看,但是外面看不到你。
嵌套的作用域也是相似的機制,只是相當于有更多的雙向鏡。
多層函數就意味著多個雙向鏡。
理解前面關于作用域的部分,就能理解閉包是什么了。
閉包
在一個函數內新建另一個函數時,就相當于創建了一個閉包。內層函數就是閉包。通常情況下,為了能夠使得外部函數的內部變量可以訪問,一般都會返回這個閉包。
因為閉包可以訪問外層函數的變量,因此他們通常有兩種用途:
減少副作用
創建私有變量
使用閉包控制副作用
當你在函數返回值時執行某些操作時,通常會發生一些副作用。副作用在很多情況下都會發生,比如Ajax調用,超時處理,或者哪怕是console.log的輸出語句:
當使用閉包來控制副作用時,實際上是需要考慮哪些可能會混淆代碼工作流程的部分,比如Ajax或者超時。
要把事情說清楚,還是看例子比較方便:
比如說你要給為你朋友慶生,做一個蛋糕。做這個蛋糕可能花1秒鐘的時間,所以寫了一個函數記錄在一秒鐘以后,記錄做完蛋糕這件事。
為了讓代碼簡短易讀,使用了ES6的箭頭函數:
但這里的問題是,并不想立刻知道蛋糕的味道。只需要知道時間到了,蛋糕做好了就行。
要解決這個問題,可以寫一個prepareCake的功能,保存蛋糕的口味。然后,在返回在內部調用prepareCake的閉包makeCake。
從這里開始,就可以在你需要的時調用,蛋糕也會在一秒后立刻做好。
這就是使用閉包減少副作用:可以創建一個任你驅使的內層閉包。
私有變量和閉包
前面已經說過,函數內的變量,在函數外部是不能訪問的既然不能訪問,那tc 就可以稱作私有變量。
確實是需要訪問私有變量的。這時候就需要閉包的幫助了。
這個例子里的saySecretCode函數,就在原函數外暴露了secretCode這一變量。因此,它也被成為特權函數。
使用DevTools調試
Chrome和Firefox的開發者工具都能很方便的調試在當前作用域內可以訪問的各種變量一般有兩種方法。
第一種方法是在代碼里使用debugger關鍵詞。這能讓瀏覽器里運行的JavaScript的暫停,以便調試。
下面是prepareCake的例子:
打開Chrome的開發者工具,定位到Source頁下(或者是Firefox的Debugger頁),你就能看到可以訪問的變量了。
使用debugger調試prepareCake的作用域。
也可以把debugger關鍵詞放在閉包內部。注意對比變量的作用域:
通過斷點調試作用域
閉包和作用域并不是那么難懂。一旦使用雙向鏡的思維去理解。
在函數里聲明一個變量時,只能在函數內訪問。這些變量的作用域就被限制在函數里了。
如果你在一個函數內又定義了內部函數,那么這個內部函數就被稱作閉包。它仍可以訪問外部函數的作用域。
0 篇文章
如果覺得我的文章對您有用,請隨意打賞。你的支持將鼓勵我繼續創作!