Javascript作用域與閉包詳情
1、作用域
簡(jiǎn)單來說,作用域是指程序中定義變量的區(qū)域,它決定了當(dāng)前執(zhí)行代碼對(duì)變量的訪問權(quán)限
在ES5中,一般只有兩種作用域類型:
- 全局作用域:全局作用域作為程序的最外層作用域,一直存在
- 函數(shù)作用域:函數(shù)作用域只有在函數(shù)被定義時(shí)才會(huì)被創(chuàng)建,包含在父級(jí)函數(shù)作用域或全局作用域中
說完概念,我們來看下面這段代碼:
var a = 100 function test(){ var b = a * 2 var a = 200 var c = a/2 console.log(b) console.log(c) } test() // 這里會(huì)打印出什么?
解析:
- 首先這段代碼形成了全局作用域與函數(shù)作用域
- 全局作用域有一個(gè)變量a賦值為100
- 在test函數(shù)作用域中定義了局部變量b,a,c
- 這里又存在變量提升,在函數(shù)作用域內(nèi)先進(jìn)行變量提升var b; var a; var c;
- 再對(duì)b進(jìn)行賦值,這時(shí)候a還沒有被賦值,所以a的值為undefined,再將a*2,所以b為NaN
- 再給a賦值為200,c賦值為a/2等于100
所以最終會(huì)打印出 NaN,100
在ES6中,新增了一種塊級(jí)作用域
簡(jiǎn)單來說,花括號(hào){...}
內(nèi)的區(qū)域就是塊級(jí)作用域,但Javascript
并不是原生支持塊級(jí)作用域的,需要借助ES6
提出的let
、const
來創(chuàng)建塊級(jí)作用域
// ES5 if(true) { var name = '南玖' } console.log(name) // 南玖 // ES6 if(true) { let age = 18 } console.log(age) // 這里會(huì)報(bào)錯(cuò)
2、作用域鏈
當(dāng)可執(zhí)行代碼內(nèi)部訪問變量時(shí),會(huì)先查找當(dāng)前作用域下有無該變量,有則立即返回,沒有的話則會(huì)去父級(jí)作用域中查找...一直找到全局作用域。我們把這種作用域的嵌套機(jī)制稱為作用域鏈
3、詞法作用域
詞法作用域
是作用域的一種工作模型,詞法作用域是JavaScript
中使用的一種作用域類型,詞法作用域也可以被叫做靜態(tài)作用域
。
所謂的詞法作用域就是在你寫代碼時(shí)將變量和作用域?qū)懺谀睦飦頉Q定的,也就是詞法作用域是靜態(tài)的作用域,在你寫代碼時(shí)就決定了。函數(shù)作用域取決于它申明的位置,與實(shí)際調(diào)用的位置無關(guān)
MDN對(duì)閉包的定義:
一個(gè)函數(shù)和對(duì)其周圍(詞法環(huán)境)的引用捆綁在一起(或者說函數(shù)被引用包圍),這樣一個(gè)組合就是閉包(closure
)
也就是說,閉包讓你可以在一個(gè)內(nèi)層函數(shù)中訪問到其外層函數(shù)的作用域。在JavaScript
中,每當(dāng)創(chuàng)建一個(gè)函數(shù),閉包就會(huì)在函數(shù)創(chuàng)建的同時(shí)被創(chuàng)建出來。
我們可以得出:
閉包 = 函數(shù) + 外層作用域
我們先來看段代碼:
var name = '前端南玖' function say() { console.log(name) } say()
解析:say
函數(shù)可以訪問到外層作用域的變量a,那么這樣不就是形成了一個(gè)閉包嗎?
在《Javascript權(quán)威指南》書中有這樣一句話:嚴(yán)格來講,所以JavaScript
函數(shù)都是閉包
但這只是理論上的閉包,與我們平時(shí)使用的不太一樣。上面這個(gè)例子只是一個(gè)簡(jiǎn)單的閉包。
ECMAScript對(duì)閉包的定義:
- 從理論上來講:所有函數(shù)都是閉包。因?yàn)樗鼈冊(cè)趧?chuàng)建的時(shí)候就已經(jīng)上層上下文的數(shù)據(jù)保存起來了。
- 從實(shí)踐上來講:閉包應(yīng)該滿足兩個(gè)條件:1.在代碼中引用了外層作用域的變量;2.即使創(chuàng)建它的上下文已經(jīng)銷毀,它仍然存在;
我們?cè)倏匆欢巍禞avaScript權(quán)威指南》上的代碼:
let scope = 'global scope' function checkscope(){ let scope = 'local scope' function f(){ return scope } return f } let s = checkscope() s() // 這里返回什么?
很多同學(xué)可能覺得是global scope
,但真的是這樣嗎,我們一起來看下它的執(zhí)行過程:
- 首先執(zhí)行全局代碼,創(chuàng)建全局執(zhí)行上下文,定義全局變量
scope
并賦值 - 申明
checkscope
函數(shù),并創(chuàng)建該函數(shù)的執(zhí)行上下文,創(chuàng)建局部變量scope
并賦值 - 申明f函數(shù),創(chuàng)建該函數(shù)的執(zhí)行上下文
- 執(zhí)行
checkscope
函數(shù),該函數(shù)又返回了一個(gè)f函數(shù)賦值給了變量s - 執(zhí)行s函數(shù),相當(dāng)于執(zhí)行了f函數(shù)。這里返回的
scope
是local scope
。至于為什么是local scope
,我們上面講到了詞法
作用的基本規(guī)則:JavaScript
函數(shù)是使用定義它們的作用域來執(zhí)行的。在定義f函數(shù)的作用域中,變量scope
的值為local scope
5、閉包的應(yīng)用
閉包的應(yīng)用,絕大多是都是在維護(hù)內(nèi)部變量的場(chǎng)景下使用
6、閉包的缺陷
- 由于閉包的存在可能會(huì)造成變量常駐內(nèi)存,使用不當(dāng)會(huì)造成內(nèi)存泄漏
- 內(nèi)存泄漏可能會(huì)導(dǎo)致應(yīng)用程序卡頓或崩潰
7、高頻閉包面試題
var arr = [] for(var i=0;i<3;i++){ arr[i] = function(){ console.log(i) } } arr[0]() // 3 arr[1]() // 3 arr[2]() // 3 // 這里在執(zhí)行的時(shí)候i已經(jīng)變成了3 // 使用閉包解決 var arr = [] for(var i=0;i<3;i++){ arr[i] = (function(i){ return function(){ console.log(i) } })(i) } arr[0]() // 0 arr[1]() // 1 arr[2]() // 2
到此這篇關(guān)于Javascript作用域與閉包詳情的文章就介紹到這了,更多相關(guān)Javascript作用域與閉包內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript中Reduce10個(gè)常用場(chǎng)景技巧
這篇文章主要為大家介紹了JavaScript中Reduce10個(gè)常用場(chǎng)景和技巧,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06特殊字符、常規(guī)符號(hào)及其代碼對(duì)照表
特殊字符、常規(guī)符號(hào)及其代碼對(duì)照表...2006-06-06實(shí)現(xiàn)一個(gè)簡(jiǎn)單得數(shù)據(jù)響應(yīng)系統(tǒng)
這篇文章主要介紹了實(shí)現(xiàn)一個(gè)簡(jiǎn)單得數(shù)據(jù)響應(yīng)系統(tǒng),文章介紹的數(shù)據(jù)響應(yīng)系統(tǒng)會(huì)用到Dep,其實(shí),這就是一個(gè)依賴收集的容器, depend 收集依賴, notify 觸發(fā)依賴,下面來看看詳細(xì)的內(nèi)容結(jié)介紹,需要的朋友可以參考一下2021-11-11