摘要:此時(shí)產(chǎn)生了閉包。導(dǎo)致,函數(shù)的活動(dòng)對(duì)象沒(méi)有被銷毀。是不是跟你想的不一樣其實(shí),這個(gè)例子重點(diǎn)就在函數(shù)上,這個(gè)函數(shù)的第一個(gè)參數(shù)接受一個(gè)函數(shù)作為回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)并不會(huì)立即執(zhí)行,它會(huì)在當(dāng)前代碼執(zhí)行完,并在給定的時(shí)間后執(zhí)行。
上一節(jié)說(shuō)了執(zhí)行上下文,這節(jié)咱們就乘勝追擊來(lái)搞搞閉包!頭疼的東西讓你不再頭疼!
一、函數(shù)也是引用類型的。function f(){ console.log("not change") }; var ff = f; function f(){ console.log("changed") }; ff(); //"changed" //ff 保存著函數(shù) f 的引用,改變f 的值, ff也變了 //來(lái)個(gè)對(duì)比,估計(jì)你就明白了。 var f = "not change"; var ff = f; f = "changed"; console.log(ff); //"not change" //ff 保存著跟 f 一樣的值,改變f 的值, ff 不會(huì)變
其實(shí),就是引用類型 和 基本類型的 區(qū)別。
function f(arg){ console.log(arg) } f(); //undefined function f(arg){ arg = 5; console.log(arg); } f(); //5
基本類型時(shí),變量保存的是數(shù)據(jù),引用類型時(shí),變量保存的是內(nèi)存地址。參數(shù)傳遞,就是把變量保存的值 復(fù)制給 參數(shù)。
var o = { a: 5 }; function f(arg){ arg.a = 6; } f(o); console.log(o.a); //6
JavaScript 具有自動(dòng)垃圾收集機(jī)制,執(zhí)行環(huán)境會(huì)負(fù)責(zé)管理代碼執(zhí)行過(guò)程中使用的內(nèi)存。函數(shù)中,正常的局部變量和函數(shù)聲明只在函數(shù)執(zhí)行的過(guò)程中存在,當(dāng)函數(shù)執(zhí)行結(jié)束后,就會(huì)釋放它們所占的內(nèi)存(銷毀變量和函數(shù))。
而js 中 主要有兩種收集方式:
標(biāo)記清除(常見(jiàn)) //給變量標(biāo)記為“進(jìn)入環(huán)境” 和 “離開(kāi)環(huán)境”,回收標(biāo)記為“離開(kāi)環(huán)境”的變量。
引用計(jì)數(shù) // 一個(gè)引用類型值,被賦值給一個(gè)變量,引用次數(shù)加1,通過(guò)變量取得引用類型值,則減1,回收為次數(shù)為0 的引用類型值。
知道個(gè)大概情況就可以了,《JavaScript高級(jí)程序設(shè)計(jì) 第三版》 4.3節(jié) 有詳解,有興趣,可以看下。.
之前說(shuō)過(guò),JavaScript中的作用域無(wú)非就是兩種:全局作用域和局部作用域。
根據(jù)作用域鏈的特性,我們知道,作用域鏈?zhǔn)菃蜗虻?。也就是說(shuō),在函數(shù)內(nèi)部,可以直接訪問(wèn)函數(shù)外部和全局變量,函數(shù)。但是,反過(guò)來(lái),函數(shù)外部和全局,是訪問(wèn)不了函數(shù)內(nèi)的變量,函數(shù)的。
function testA(){ var a = 666; } console.log(a); //報(bào)錯(cuò),a is not defined var b = 566; function testB(){ console.log(b); } //566
但是,有時(shí)候,我們需要在函數(shù)外部 訪問(wèn)函數(shù)內(nèi)部的變量,函數(shù)。一般情況下,我們是辦不到的,這時(shí),我們就需要閉包來(lái)實(shí)現(xiàn)了。
function fa(){ var va = "this is fa"; function fb(){ console.log(va); } return fb; } var fc = fa(); fc(); //"this is fa"
想要讀取fa 函數(shù)內(nèi)的變量 va,我們?cè)趦?nèi)部定義了一個(gè)函數(shù) fb,但是不執(zhí)行它,把它返回給外部,用 變量fc接受。此時(shí),在外部再執(zhí)行fc,就讀取了fa 函數(shù)內(nèi)的變量 va。
其實(shí),簡(jiǎn)單點(diǎn)說(shuō),就是在 A 函數(shù)內(nèi)部,存在 B 函數(shù), B函數(shù) 在 A 函數(shù) 執(zhí)行完畢后再執(zhí)行。B執(zhí)行時(shí),訪問(wèn)了已經(jīng)執(zhí)行完畢的 A函數(shù)內(nèi)部的變量和函數(shù)。
由此可知:閉包是函數(shù)A的執(zhí)行環(huán)境 以及 執(zhí)行環(huán)境中的函數(shù) B組合而構(gòu)成的。
上篇文章中說(shuō)過(guò),變量等 都儲(chǔ)存在 其所在執(zhí)行環(huán)境的活動(dòng)對(duì)象中,所以說(shuō)是 函數(shù)A 的執(zhí)行環(huán)境。
當(dāng) 函數(shù)A執(zhí)行完畢后,函數(shù)B再執(zhí)行,B的作用域中就保留著 函數(shù)A 的活動(dòng)對(duì)象,因此B中可以訪問(wèn) A中的 變量,函數(shù),arguments對(duì)象。此時(shí)產(chǎn)生了閉包。大部分書(shū)中,都把 函數(shù)B 稱為閉包,而在谷歌瀏覽器中,把 A函數(shù)稱為閉包。
之前說(shuō)過(guò),當(dāng)函數(shù)執(zhí)行完畢后,局部活動(dòng)對(duì)象就會(huì)被銷毀。其中保存的變量,函數(shù)都會(huì)被銷毀。內(nèi)存中僅保存全局作用域(全局執(zhí)行環(huán)境的變量對(duì)象)。但是,閉包的情況就不同了。
以上面的例子來(lái)說(shuō),函數(shù)fb 和其所在的環(huán)境 函數(shù)fa,就組成了閉包。函數(shù)fa執(zhí)行完畢后,按道理說(shuō), 函數(shù)fa 執(zhí)行環(huán)境中的 活動(dòng)對(duì)象就應(yīng)該被銷毀了。但是,因?yàn)?函數(shù)fa 執(zhí)行時(shí),其中的 函數(shù)fb 被 返回,被 變量fc 引用著。導(dǎo)致,函數(shù)fa 的活動(dòng)對(duì)象沒(méi)有被銷毀。而在其后 fc() 執(zhí)行,就是 函數(shù)fb 執(zhí)行時(shí),構(gòu)建的作用域中保存著 函數(shù)fa 的活動(dòng)對(duì)象,因此,函數(shù)fb 中 可以通過(guò)作用域鏈訪問(wèn) 函數(shù)fa 中的變量。
我已經(jīng)盡力地說(shuō)明白了。就看各位的了。哈哈!其實(shí),簡(jiǎn)單的說(shuō):就是fa函數(shù)執(zhí)行完畢了,其內(nèi)部的 fb函數(shù)沒(méi)有執(zhí)行,并返回fb的引用,當(dāng)fb再次執(zhí)行時(shí),fb的作用域中保留著 fa函數(shù)的活動(dòng)對(duì)象。
再來(lái)個(gè)有趣經(jīng)典的例子:
for (var i=1; i<=5; i++) { setTimeout(function(){ console.log(i); },i*1000); } //每隔一秒輸出一個(gè)6,共5個(gè)。
是不是跟你想的不一樣?其實(shí),這個(gè)例子重點(diǎn)就在setTimeout函數(shù)上,這個(gè)函數(shù)的第一個(gè)參數(shù)接受一個(gè)函數(shù)作為回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)并不會(huì)立即執(zhí)行,它會(huì)在當(dāng)前代碼執(zhí)行完,并在給定的時(shí)間后執(zhí)行。這樣就導(dǎo)致了上面情況的發(fā)生。
可以下面對(duì)這個(gè)例子進(jìn)行變形,可以有助于你的理解把:
var i = 1; while(i <= 5){ setTimeout(function(){ console.log(i); },i*1000) i = i+1; }
正因?yàn)椋?b>setTimeout里的第一個(gè)函數(shù)不會(huì)立即執(zhí)行,當(dāng)這段代碼執(zhí)行完之后,i 已經(jīng) 被賦值為6了(等于5時(shí),進(jìn)入循環(huán),最后又加了1),所以 這時(shí)再執(zhí)行setTimeout 的回調(diào)函數(shù),讀取 i 的值,回調(diào)函數(shù)作用域內(nèi)沒(méi)有i,向上讀取,上面作用域內(nèi)i的值就是6了。但是 i * 1000,是立即執(zhí)行的,所以,每次讀的 i 值 都是對(duì)的。
這時(shí)候,就需要利用閉包來(lái)保存每個(gè)循環(huán)時(shí), i 不同的值。
function makeClosures(i){ //這里就和 內(nèi)部的匿名函數(shù)構(gòu)成閉包了 var i = i; //這步是不需要的,為了讓看客們看的輕松點(diǎn) return function(){ console.log(i); //匿名沒(méi)有執(zhí)行,它可以訪問(wèn)i 的值,保存著這個(gè)i 的值。 } } for (var i=1; i<=5; i++) { setTimeout(makeClosures(i),i*1000); //這里簡(jiǎn)單說(shuō)下,這里makeClosures(i), 是函數(shù)執(zhí)行,并不是傳參,不是一個(gè)概念 //每次循環(huán)時(shí),都執(zhí)行了makeClosures函數(shù),都返回了一個(gè)沒(méi)有被執(zhí)行的匿名函數(shù) //(這里就是返回了5個(gè)匿名函數(shù)),每個(gè)匿名函數(shù)都是一個(gè)局部作用域,保存著每次傳進(jìn)來(lái)的i值 //因此,每個(gè)匿名函數(shù)執(zhí)行時(shí),讀取`i`值,都是自己作用域內(nèi)保存的值,是不一樣的。所以,就得到了想要的結(jié)果 } //1 //2 //3 //4 //5
閉包的關(guān)鍵就在,外部的函數(shù)執(zhí)行完畢后,內(nèi)部的函數(shù)再執(zhí)行,并訪問(wèn)了外部函數(shù)內(nèi)的變量。
你可能在別處,或者自己想到了下面這種解法:
for (var i=1; i<=5; i++) { (function(i){ setTimeout(function(){ console.log(i); },i*1000); })(i); }
如果你一直把這個(gè)當(dāng)做閉包,那你可能看到的是不同的閉包定義吧(犀牛書(shū)和高程對(duì)閉包的定義不同)。嚴(yán)格來(lái)說(shuō),這不是閉包,這是利用了立即執(zhí)行函數(shù) 和 函數(shù)作用域 來(lái)解決的。
做下變形,你再看看:
for (var i=1; i<=5; i++) { function f(i){ setTimeout(function(){ console.log(i); },i*1000); }; f(i); }
這樣看就很明顯了吧,主要是利用了函數(shù)作用域,而使用立即執(zhí)行函數(shù),是為了簡(jiǎn)化步驟。
總結(jié):判斷是不是閉包,我總結(jié)了要滿足以下三點(diǎn):
兩個(gè)函數(shù)。有內(nèi)函數(shù) 和 外函數(shù)。
外函數(shù)執(zhí)行完畢后,內(nèi)函數(shù) 還沒(méi)有執(zhí)行。
當(dāng)內(nèi)函數(shù)執(zhí)行時(shí)(通過(guò)外部引用或者返回內(nèi)函數(shù)),訪問(wèn)了 外函數(shù)內(nèi)部的 變量,函數(shù)等(說(shuō)是訪問(wèn),其實(shí)內(nèi)函數(shù)保存著外函數(shù)的活動(dòng)對(duì)象,因此,arguments對(duì)象也可以訪問(wèn)到)。
其實(shí)這道題,知道ES6的 let 關(guān)鍵詞,估計(jì)也想到了另一個(gè)解法:
for (let i=1; i<=5; i++) { //這里的關(guān)鍵就是使用的let 關(guān)鍵詞,來(lái)形成塊級(jí)作用域 setTimeout(function(){ console.log(i); },i*1000); }
我不知道,大家有沒(méi)有疑惑啊,為啥使用了塊級(jí)作用域就可以了呢。反正我當(dāng)初就糾結(jié)了半天。
11月 2日修正:
這個(gè)答案的關(guān)鍵就在于 塊級(jí)作用域的規(guī)則了。它讓let 聲明的變量只在{}內(nèi)有效,外部是訪問(wèn)不了的。
做下變形:
for (var i=1; i<=5; i++) { let j = i; setTimeout(function(){ console.log(j); },j*1000); }
其實(shí),for 循環(huán)時(shí),每次都會(huì)用let 或者 var 創(chuàng)建一個(gè)新變量,并以之前迭代中同名變量的值將其初始化。而這里正因?yàn)槭褂胠et,導(dǎo)致每次循環(huán)都會(huì)創(chuàng)建一個(gè)新的塊級(jí)作用域,這樣,雖然setTimeout 中的匿名函數(shù)內(nèi)沒(méi)有 i 值,但它向上作用域讀取i 值,就讀到了塊級(jí)作用域內(nèi) i 的值。
上面用立即執(zhí)行函數(shù)模擬塊級(jí)作用域,就是這個(gè)道理啦!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/96109.html
摘要:為了更好的理解,在閱讀此文之前建議先閱讀上一篇進(jìn)擊之詞法作用域與作用域鏈?zhǔn)裁词情]包閉包的含義就是閉合,包起來(lái),簡(jiǎn)單的來(lái)說(shuō),就是一個(gè)具有封閉功能與包裹功能的結(jié)構(gòu)。在中函數(shù)構(gòu)成閉包。 為了更好的理解,在閱讀此文之前建議先閱讀上一篇《進(jìn)擊JavaScript之詞法作用域與作用域鏈》 1.什么是閉包 閉包的含義就是閉合,包起來(lái),簡(jiǎn)單的來(lái)說(shuō),就是一個(gè)具有封閉功能與包裹功能的結(jié)構(gòu)。所謂的閉包就是...
摘要:匿名函數(shù)是不能單獨(dú)寫的,所以就提不上立即執(zhí)行了。六立即執(zhí)行函數(shù)在閉包中的應(yīng)用立即執(zhí)行函數(shù)能配合閉包保存狀態(tài)。來(lái)看下上節(jié)內(nèi)容中閉包的例子現(xiàn)在,我們來(lái)利用立即執(zhí)行函數(shù)來(lái)簡(jiǎn)化它第一個(gè)匿名函數(shù)執(zhí)行完畢后,返回了第二個(gè)匿名函數(shù)。 前面的閉包中,提到與閉包相似的立即執(zhí)行函數(shù),感覺(jué)兩者還是比較容易弄混吧,嚴(yán)格來(lái)說(shuō)(因?yàn)橄?shū)和高程對(duì)閉包的定義不同),立即執(zhí)行函數(shù)并不屬于閉包,它不滿足閉包的三個(gè)條件。...
摘要:如下代碼輸出的結(jié)果是代碼執(zhí)行分為兩個(gè)大步預(yù)解析的過(guò)程代碼的執(zhí)行過(guò)程預(yù)解析與變量聲明提升程序在執(zhí)行過(guò)程中,會(huì)先將代碼讀取到內(nèi)存中檢查,會(huì)將所有的聲明在此進(jìn)行標(biāo)記,所謂的標(biāo)記就是讓解析器知道有這個(gè)名字,后面在使用名字的時(shí)候不會(huì)出現(xiàn)未定義的錯(cuò)誤。 showImg(https://segmentfault.com/img/remote/1460000012922850); 如下代碼輸出的結(jié)果是...
摘要:每一個(gè)由構(gòu)造函數(shù)創(chuàng)建的對(duì)象都會(huì)默認(rèn)的連接到該神秘對(duì)象上。在構(gòu)造方法中也具有類似的功能,因此也稱其為類實(shí)例與對(duì)象實(shí)例一般是指某一個(gè)構(gòu)造函數(shù)創(chuàng)建出來(lái)的對(duì)象,我們稱為構(gòu)造函數(shù)的實(shí)例實(shí)例就是對(duì)象。表示該原型是與什么構(gòu)造函數(shù)聯(lián)系起來(lái)的。 本文您將看到以下內(nèi)容: 傳統(tǒng)構(gòu)造函數(shù)的問(wèn)題 一些相關(guān)概念 認(rèn)識(shí)原型 構(gòu)造、原型、實(shí)例三角結(jié)構(gòu)圖 對(duì)象的原型鏈 函數(shù)的構(gòu)造函數(shù)Function 一句話說(shuō)明什么...
摘要:一作用域域表示的就是范圍,即作用域,就是一個(gè)名字在什么地方可以使用,什么時(shí)候不能使用。概括的說(shuō)作用域就是一套設(shè)計(jì)良好的規(guī)則來(lái)存儲(chǔ)變量,并且之后可以方便地找到這些變量。 一、作用域 域表示的就是范圍,即作用域,就是一個(gè)名字在什么地方可以使用,什么時(shí)候不能使用。想了解更多關(guān)于作用域的問(wèn)題推薦閱讀《你不知道的JavaScript上卷》第一章(或第一部分),從編譯原理的角度說(shuō)明什么是作用域。概...
閱讀 1165·2021-11-25 09:43
閱讀 2979·2019-08-30 15:54
閱讀 3363·2019-08-30 15:54
閱讀 3015·2019-08-30 15:44
閱讀 1636·2019-08-26 12:18
閱讀 2266·2019-08-26 11:42
閱讀 887·2019-08-26 11:35
閱讀 3306·2019-08-23 18:22