關(guān)于 javascript 閉包的一些思考
什么是作用域?
- 作用域
- 眾所周知 在javascript 作用域就是限制我們執(zhí)行代碼的一個(gè)范圍,或者說(shuō)是框架。
- 首先來(lái)談?wù)刯s的編譯原理,其中不可避免的就要提到 引擎、編譯器、作用域
- 引擎:負(fù)責(zé)整個(gè) JavaScript 程序的編譯及執(zhí)行過(guò)程
- 編譯器:負(fù)責(zé)語(yǔ)法分析及代碼生成
- 作用域: 負(fù)責(zé)收集并維護(hù)由所有聲明的標(biāo)識(shí)符(變量)組成的一系列查 詢,并實(shí)施一套非常嚴(yán)格的規(guī)則,確定當(dāng)前執(zhí)行的代碼對(duì)這些標(biāo)識(shí)符的訪問(wèn)權(quán)限
- 這次我們的主角是==作用域==
-
下面我們來(lái)看一個(gè)簡(jiǎn)單的賦值操作:
var a = 2;- 然后他們?nèi)齻€(gè)都干了什么啦?
- 變量的賦值操作會(huì)執(zhí)行兩個(gè)動(dòng)作,首先編譯器會(huì)在當(dāng)前作用域中聲明一個(gè)變量(如 果之前沒(méi)有聲明過(guò)),然后在運(yùn)行時(shí)引擎會(huì)在作用域中查找該變量,如果能夠找到就會(huì)對(duì) 它賦值。
- 然后他們?nèi)齻€(gè)都干了什么啦?
- 當(dāng)然我們不必深究他們到底干了什么,我們要深入探究 作用域到底是什么?
什么是詞法作用域?
- 詞法作用域
-
我們來(lái)先看看一段代碼
function foo(a) { var b = a * 2; function bar(c) { console.log( a, b, c ); } bar( b * 3 ); } foo( 2 ); // 2, 4, 121 包含著整個(gè)全局作用域,其中只有一個(gè)標(biāo)識(shí)符:foo。
2 ?包含著 foo 所創(chuàng)建的作用域,其中有三個(gè)標(biāo)識(shí)符:a、bar 和 b。
3 ?包含著 bar 所創(chuàng)建的作用域,其中只有一個(gè)標(biāo)識(shí)符:c。
- 上面描述的就是作用域的作用,每個(gè)標(biāo)識(shí)符都對(duì)應(yīng)著相應(yīng)的。我們==把作用域看做一個(gè)氣泡==
-
什么是函數(shù)作用域?
- 函數(shù)作用域
- 如同上面的詞法作用域那樣,在 javascript 中當(dāng)我們創(chuàng)建一個(gè)函數(shù)的時(shí)候都會(huì)創(chuàng)建一個(gè)新的作用域。
function foo(a){ var b = 2; //一些代碼 function bar(){ // ... } // 更多的代碼 var a = 3; }- 在這個(gè)代碼片段中,==foo(..) 的作用域氣泡中包含了標(biāo)識(shí)符 a、b、c 和 bar==。無(wú)論標(biāo)識(shí)符 聲明出現(xiàn)在作用域中的何處,這個(gè)標(biāo)識(shí)符所代表的變量或函數(shù)都將附屬于所處作用域的氣泡。
什么是塊作用域?
- 塊作用域
- 除 JavaScript 外的很多編程語(yǔ)言都支持塊作用域,因此其他語(yǔ)言的開(kāi)發(fā)者對(duì)于相關(guān)的思維 方式會(huì)很熟悉,但是對(duì)于主要使用 JavaScript 的開(kāi)發(fā)者來(lái)說(shuō),這個(gè)概念會(huì)很陌生。
- 看下面的代碼
for (var i=0; i<10; i++) { console.log(i) }- 我們?cè)趂or的頭部定義了變量 i ,而且我們只想在for循環(huán)中使用 i ,而忽略了在 javascript 中 i 會(huì)綁定在全局變量(外部作用域)中。
-
在 ES6 中我們推薦使用 let 來(lái)避免 i收到全局變量的污染
for(let i = 0;i < 10; i++ ){ console.log(i); }
- 除 JavaScript 外的很多編程語(yǔ)言都支持塊作用域,因此其他語(yǔ)言的開(kāi)發(fā)者對(duì)于相關(guān)的思維 方式會(huì)很熟悉,但是對(duì)于主要使用 JavaScript 的開(kāi)發(fā)者來(lái)說(shuō),這個(gè)概念會(huì)很陌生。
什么是垃圾回收機(jī)制
- 垃圾回收機(jī)制
- JavaScript 垃圾回收的機(jī)制很簡(jiǎn)單:找出==不再使用的變量==,然后釋放掉其占用的內(nèi)存,但是這個(gè)過(guò)程不是時(shí)時(shí)的,因?yàn)槠溟_(kāi)銷比較大,所以垃圾回收器會(huì)按照固定的時(shí)間間隔周期性的執(zhí)行。
- 那什么是==不再使用的變量==啦?
- 我們知道js中的全局變量,和局部變量。全局變量在瀏覽器頁(yè)面卸載的時(shí)候才會(huì)回收。而局部變量在函數(shù)生命周期結(jié)束的時(shí)候?yàn)g覽器為了節(jié)約內(nèi)存空間,就需要回收這一變量。
-
一種回收方法-標(biāo)記清除(mark and sweep)
- 這是JavaScript最常見(jiàn)的垃圾回收方式,當(dāng)變量進(jìn)入執(zhí)行環(huán)境的時(shí)候,比如函數(shù)中聲明一個(gè)變量,垃圾回收器將其標(biāo)記為“進(jìn)入環(huán)境”,當(dāng)變量離開(kāi)環(huán)境的時(shí)候(函數(shù)執(zhí)行結(jié)束)將其標(biāo)記為“離開(kāi)環(huán)境”。
- 還有其他的回收的方法就不多多探究了。
什么是閉包?
- 閉包的理解
-
作用域閉包
- 參考阮一峰大神的文章,代碼中的f2函數(shù),就是閉包。
function f1(){ var n = 999; function f2(){ alert(n); } return f2; } var result = f1(); result();//999閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)。
由于在Javascript語(yǔ)言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,因此可以把閉包簡(jiǎn)單理解成"定義在一個(gè)函數(shù)內(nèi)部的函數(shù)"。
所以,在本質(zhì)上,閉包就是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的一座橋梁。
-
閉包的用途
- 一個(gè)是前面提到的可以讀取函數(shù)內(nèi)部的變量
- 另一個(gè)就是讓這些變量的值始終保持在內(nèi)存中。
function f1(){ var n=999; nAdd=function(){n+=1} function f2(){ alert(n); } return f2; } var result=f1(); result(); // 999 nAdd(); result(); // 1000- result實(shí)際上就是閉包f2函數(shù)。它一共運(yùn)行了兩次,第一次的值是999,第二次的值是1000。這證明了,函數(shù)f1中的局部變量n一直保存在內(nèi)存中,并沒(méi)有在f1調(diào)用后被自動(dòng)清除。
- 原因就在于f1是f2的父函數(shù),而f2被賦給了一個(gè)全局變量,這導(dǎo)致f2始終在內(nèi)存中,而f2的存在依賴于f1,因此f1也始終在內(nèi)存中,不會(huì)在調(diào)用結(jié)束后,被垃圾回收機(jī)制(garbage collection)回收。
-