javascript
【javascript基础】8、闭包
前言
函數(shù)和作用域啥的我們前面已經(jīng)了解了,現(xiàn)在就要學(xué)習(xí)閉包了,這是一個(gè)挺晦澀的知識(shí)點(diǎn),初學(xué)者可能會(huì)感覺不好理解,但是高手都不不以為然了,高手就給我提點(diǎn)意見吧,我和新手一起來學(xué)習(xí)什么是閉包。
例子
先不說定義,先看一個(gè)題,看看大家能得出正確的結(jié)果不,
function test(){var arr = [];for(var i = 0;i<10;i++){arr[i] = function(){return i;}}return arr; }var fns = test(); console.log(fns[9]()); // 值是多少? console.log(fns[0]());//值是多少?結(jié)果就是
10 10 View Code你做對(duì)了嗎?
?
什么是閉包
我們知道,javascript中的變量作用域分為全局變量和局部變量,全局的變量我們?cè)谑裁吹胤蕉伎梢允褂?#xff0c;但是局部變量就不是這樣的了,我們只能在該變量的作用域中得到,換句話說就是我們?cè)诤瘮?shù)的內(nèi)部可以使用函數(shù)外部的變量,但是我們?cè)诤瘮?shù)的外部卻不能使用函數(shù)內(nèi)部定義的局部變量,但是在實(shí)際中我們就是想要在函數(shù)的外部使用函數(shù)內(nèi)部定義的變量那該怎么辦呢?例子來了
function test(){var inner = 10; } alert(inner);//error?咋辦咋辦呢?我們知道,在內(nèi)部我們可以訪問到這個(gè)變量,我們還知道有一個(gè)操作符return可以返回想要的值,那我就在內(nèi)部定義一個(gè)函數(shù)來訪問這個(gè)變量,然后在返回這個(gè)函數(shù)不就行了,實(shí)踐一下
function test(){var inner = 10;function inFun(){alert(inner);// };return inFun; } var outter = test(); outter();//10;我們做到了,為自己鼓鼓掌,有時(shí)候我們就該不斷鼓勵(lì)自己一下,不要給自己太大的壓力,我們不是富二代,在不鼓勵(lì)一下自己怎么能成為富二代他爹呢。
這就是閉包了,官方?jīng)]有給出閉包一個(gè)完整的準(zhǔn)確的定義,民間流傳的是在一個(gè)函數(shù)內(nèi)定義一個(gè)函數(shù),并且這個(gè)內(nèi)部函數(shù)可以在外面訪問,這時(shí)候就形成了閉包??纯瓷厦婧瘮?shù)的結(jié)構(gòu),一個(gè)函數(shù)返回了一個(gè)內(nèi)部函數(shù),我們知道在正常情況下,一個(gè)函數(shù)執(zhí)行結(jié)束之后,里面的變量會(huì)被釋放,也就是說,在test()這句執(zhí)行之后,里面的inner應(yīng)該被釋放了才對(duì),但是我們發(fā)現(xiàn),outter()時(shí)我們拿到了inner的值,這就是閉包的特性:如果閉包中使用了局部的變量,那么這個(gè)變量會(huì)一直貯存在內(nèi)存中,閉包會(huì)一直保持這個(gè)值,一直到外部的函數(shù)沒有被引用為止,看例子
function closure(){var num = 0;function add(){console.log(++num);}return add; } var test1 = closure();//形成一個(gè)閉包,保持著自己的一個(gè)num變量 test1 ();//1 test1 ();//2 var test2 = closure();//又一個(gè)閉包,保持了一個(gè)自己的num變量 test2 ();//1 test2 ();//2好玩不?這就是閉包的神奇的地方,也是讓身為初學(xué)者的我們感到彷徨的地方,相信我,我會(huì)讓你們理解明白的。要想釋放num占用的內(nèi)存,就該這樣
test1 = null; test2 = null;簡(jiǎn)單解析下這個(gè)例子:在執(zhí)行?var test1 = closure()時(shí),由于closure()返回到是一個(gè)函數(shù),這里就相當(dāng)于test1變量指向了一個(gè)函數(shù)add,但是這個(gè)add函數(shù)有自己的作用域和活動(dòng)對(duì)象,都存在了test1中,執(zhí)行test1()時(shí),會(huì)尋找num變量,由于閉包存儲(chǔ)了該變量就可以直接取到,并且自加1,再一次執(zhí)行test1()時(shí)會(huì)繼續(xù)在test1執(zhí)行的add函數(shù)的執(zhí)行環(huán)境和作用域中查找,發(fā)現(xiàn)num為1了,就找到了這個(gè)num;在執(zhí)行var test2 = closure()時(shí),會(huì)重新創(chuàng)建一個(gè)閉包,重新存儲(chǔ)執(zhí)行環(huán)境和活動(dòng)對(duì)象,所以這是和第一次完全沒有關(guān)系的。
閉包的機(jī)制
函數(shù)也是對(duì)象,有[[scope]]屬性(只能通過JavaScript引擎訪問),指向函數(shù)定義時(shí)的執(zhí)行環(huán)境上下文。
假如A是全局的函數(shù),B是A的內(nèi)部函數(shù)。執(zhí)行A函數(shù)時(shí),當(dāng)前執(zhí)行環(huán)境的上下文指向一個(gè)作用域鏈。作用域鏈的第一個(gè)對(duì)象是當(dāng)前函數(shù)的活動(dòng)對(duì)象(this、參數(shù)、局部變量),第二個(gè)對(duì)象是全局window。
當(dāng)執(zhí)行代碼運(yùn)行到B定義地方, 設(shè)置函數(shù)B的[[scope]]屬性指向執(zhí)行環(huán)境的上下文作用域鏈。
執(zhí)行A函數(shù)完畢后,若內(nèi)部函數(shù)B的引用沒外暴,A函數(shù)活動(dòng)對(duì)象將被Js垃圾回收處理;反之,則維持,形成閉包。
調(diào)用函數(shù)B時(shí),JavaScript引擎將當(dāng)前執(zhí)行環(huán)境入棧,生成新的執(zhí)行環(huán)境,新的執(zhí)行環(huán)境的上下文指向一個(gè)作用域鏈,由當(dāng)前活動(dòng)對(duì)象+函數(shù)B的[[scope]]組成,鏈的第一個(gè)對(duì)象是當(dāng)前函數(shù)的活動(dòng)對(duì)象(this、參數(shù)、局部變量組成),第二個(gè)活動(dòng)對(duì)象是A函數(shù)產(chǎn)生的,第三個(gè)window。
B函數(shù)里面訪問一個(gè)變量,要進(jìn)行標(biāo)志符解析(JavaScript原型也有標(biāo)識(shí)符解析),它從當(dāng)前上下文指向的作用域鏈的第一個(gè)對(duì)象開始查找,找不到就查找第二個(gè)對(duì)象,直到找到相關(guān)值就立即返回,如果還沒找到,報(bào)undefined錯(cuò)誤。
當(dāng)有關(guān)A函數(shù)的外暴的內(nèi)部引用全部被消除時(shí),A的活動(dòng)對(duì)象才被銷毀。
這段是其他的地方的,就是說了執(zhí)行環(huán)境和作用域的理解閉包怎么維持變量的。
閉包的應(yīng)用
一個(gè)是前面提到的可以讀取函數(shù)內(nèi)部的變量,另一個(gè)就是讓這些變量的值始終保持在內(nèi)存中,這既是函數(shù)也是弊端。我們可以利用閉包封裝一些私有的屬性,例如
var factorial = (function () {var cache = [];return function (num) {if (!cache[num]) {if (num == 0) {cache[num] = 1;}cache[num] = num * factorial(num - 1);}return cache[num];} })();封裝了一個(gè)內(nèi)部私有的屬性來緩存結(jié)果。
下面流行的模塊模式,它允許你模擬公共,私有以及特權(quán)成員
var Module = (function(){var privateProperty = 'foo';function privateMethod(args){//do something }return {publicProperty: "",publicMethod: function(args){//do something },privilegedMethod: function(args){privateMethod(args);}} })();另一個(gè)類型的閉包叫做立即執(zhí)行函數(shù)表達(dá)式,是一個(gè)在window上下文中自我調(diào)用的匿名函數(shù):
(function(window){var a = 'foo';function private(){// do something }window.Module = {public: function(){// do something }};})(this);?
閉包的弊端
由于閉包會(huì)使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會(huì)造成網(wǎng)頁的性能問題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除。閉包會(huì)在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。所以,如果你把父函數(shù)當(dāng)作對(duì)象(object)使用,把閉包當(dāng)作它的公用方法(Public Method),把內(nèi)部變量當(dāng)作它的私有屬性(private value),這時(shí)一定要小心,不要隨便改變父函數(shù)內(nèi)部變量的值。
解釋例子
回到開始的例子,這是閉包的經(jīng)典的例子,這個(gè)和其他的例子和有些不一樣,我們分析一下,這里用了一個(gè)數(shù)組,其實(shí)這里我們執(zhí)行一次var fns = test(),形成了10個(gè)閉包,數(shù)組的每一個(gè)項(xiàng)存了一個(gè)閉包,這與其他的例子是不一樣的,其他的例子是函數(shù)執(zhí)行一次形成了一個(gè)閉包,所以這個(gè)10個(gè)閉包的初始的執(zhí)行環(huán)境是一樣的,每一個(gè)閉包使用了i這個(gè)變量,這個(gè)變量在函數(shù)var?fns =?test()執(zhí)行之后變?yōu)榱送顺鲅h(huán)的那個(gè)i的值10,JavaScript是解釋型的語言,所以在執(zhí)行數(shù)組中的閉包的時(shí),會(huì)找到此時(shí)i的值10;看看arr的結(jié)果
現(xiàn)在想怎樣解決這個(gè)問題呢?我們想想,這10個(gè)閉包形成時(shí)的執(zhí)行環(huán)境和活動(dòng)對(duì)象是一樣的,現(xiàn)在考慮的就是要在初始時(shí)就不一樣,我們知道函數(shù)的作用域是一層一層的,那我們就需要在這之間家一層作用域,這層作用域要有不同的i的值,我們想到了自執(zhí)行匿名函數(shù),(funciton(){})(),我們把i的值穿進(jìn)去,按值傳參就是相當(dāng)于復(fù)制了一份變量嘛,在(funciton(){})()外部的作用域中的i的值的改變不會(huì)改變內(nèi)部的i的值,試一下
function test(){var arr = [];for(var i = 0;i<10;i++){(function(i){ arr[i] = function(){return i;}})(i);}return arr; }var fns = test(); console.log(fns[9]()); // 值是9 console.log(fns[0]());//值是0當(dāng)然也可以這樣
function test(){var arr = [];for(var i = 0;i<10;i++){arr[i] = (function(i){return function(){return i}})(i);}return arr; }var fns = test(); console.log(fns[9]()); // 值是9 console.log(fns[0]());//值是0這兩個(gè)的實(shí)質(zhì)都是在閉包形成之前,給每一個(gè)閉包包上一層作用域,在這個(gè)作用域中傳一個(gè)參數(shù),是每一個(gè)閉包上一級(jí)的作用域中都有不同的i。當(dāng)然還有其他的辦法這里不說了。
小結(jié)
閉包的應(yīng)用場(chǎng)景挺多的,在模塊化編程中很重要的,有些地方說函數(shù)也是閉包,還是那就話,概念不重要,理解會(huì)用才是最現(xiàn)實(shí)的。
?
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/allenxing/p/3578914.html
總結(jié)
以上是生活随笔為你收集整理的【javascript基础】8、闭包的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: apache的prefork和workd
- 下一篇: 使用Chrome工具来分析页面的绘制状态