摘要:再看一段代碼這樣就清晰地展示了閉包的詞法作用域能訪問的作用域?qū)?dāng)做一個(gè)值返回執(zhí)行后,將的引用賦值給執(zhí)行,輸出了變量我們知道通過引用的關(guān)系,就是函數(shù)本身。
在正式學(xué)習(xí)閉包之前,請(qǐng)各位同學(xué)一定要確保自己對(duì)詞法作用域已經(jīng)非常的熟悉了,如果對(duì)詞法作用域還不夠熟悉的話,可以先看:
深入理解閉包之前置知識(shí)---作用域與詞法作用域
前言現(xiàn)在去面試前端開發(fā)的崗位,如果你的面試官也是個(gè)前端,并且不是太水的話,你有很大的概率會(huì)被問到JavaScript中的閉包。因?yàn)檫@個(gè)閉包這個(gè)知識(shí)點(diǎn)真的很重要,還非常難掌握。
什么是閉包什么是閉包,你可能會(huì)搜出很多答案....
《JavaScript高級(jí)程序設(shè)計(jì)》這樣描述:
閉包是指有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù);
《JavaScript權(quán)威指南》這樣描述:
從技術(shù)的角度講,所有的JavaScript函數(shù)都是閉包:它們都是對(duì)象,它們都關(guān)聯(lián)到作用域鏈。
《你不知道的JavaScript》這樣描述:
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。
我最認(rèn)同的是《你不知道的JavaScript》中的描述,雖然前面的兩種說法都沒有錯(cuò),但閉包應(yīng)該是基于詞法作用域書寫代碼時(shí)產(chǎn)生的自然結(jié)果,是一種現(xiàn)象!你也不用為了利用閉包而特意的創(chuàng)建,因?yàn)殚]包的在你的代碼中隨處可見,只是你還不知道當(dāng)時(shí)你寫的那一段代碼其實(shí)就產(chǎn)生了閉包。
講解閉包上面已經(jīng)說到,當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時(shí),就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。
看一段代碼
function fn1() { var name = "iceman"; function fn2() { console.log(name); } fn2(); } fn1();
如果是根據(jù)《JavaScript高級(jí)程序設(shè)計(jì)》和《JavaScript權(quán)威指南》來說,上面的代碼已經(jīng)產(chǎn)生閉包了。fn2訪問到了fn1的變量,滿足了條件“有權(quán)訪問另一個(gè)函數(shù)作用域中的變量的函數(shù)”,fn2本身是個(gè)函數(shù),所以滿足了條件“所有的JavaScript函數(shù)都是閉包”。
這的確是閉包,但是這種方式定義的閉包不太好觀察。
再看一段代碼:
function fn1() { var name = "iceman"; function fn2() { console.log(name); } return fn2; } var fn3 = fn1(); fn3();
這樣就清晰地展示了閉包:
fn2的詞法作用域能訪問fn1的作用域
將fn2當(dāng)做一個(gè)值返回
fn1執(zhí)行后,將fn2的引用賦值給fn3
執(zhí)行fn3,輸出了變量name
我們知道通過引用的關(guān)系,fn3就是fn2函數(shù)本身。執(zhí)行fn3能正常輸出name,這不就是fn2能記住并訪問它所在的詞法作用域,而且fn2函數(shù)的運(yùn)行還是在當(dāng)前詞法作用域之外了。
正常來說,當(dāng)fn1函數(shù)執(zhí)行完畢之后,其作用域是會(huì)被銷毀的,然后垃圾回收器會(huì)釋放那段內(nèi)存空間。而閉包卻很神奇的將fn1的作用域存活了下來,fn2依然持有該作用域的引用,這個(gè)引用就是閉包。
總結(jié):某個(gè)函數(shù)在定義時(shí)的詞法作用域之外的地方被調(diào)用,閉包可以使該函數(shù)極限訪問定義時(shí)的詞法作用域。
注意:對(duì)函數(shù)值的傳遞可以通過其他的方式,并不一定值有返回該函數(shù)這一條路,比如可以用回調(diào)函數(shù):
function fn1() { var name = "iceman"; function fn2() { console.log(name); } fn3(fn2); } function fn3(fn) { fn(); } fn1();
本例中,將內(nèi)部函數(shù)fn2傳遞給fn3,當(dāng)它在fn3中被運(yùn)行時(shí),它是可以訪問到name變量的。
所以無論通過哪種方式將內(nèi)部的函數(shù)傳遞到所在的詞法作用域以外,它都回持有對(duì)原始作用域的引用,無論在何處執(zhí)行這個(gè)函數(shù)都會(huì)使用閉包。
再次解釋閉包以上的例子會(huì)讓人覺得有點(diǎn)學(xué)院派了,但是閉包絕不僅僅是一個(gè)無用的概念,你寫過的代碼當(dāng)中肯定有閉包的身影,比如類似如下的代碼:
function waitSomeTime(msg, time) { setTimeout(function () { console.log(msg) }, time); } waitSomeTime("hello", 1000);
定時(shí)器中有一個(gè)匿名函數(shù),該匿名函數(shù)就有涵蓋waitSomeTime函數(shù)作用域的閉包,因此當(dāng)1秒之后,該匿名函數(shù)能輸出msg。
另一個(gè)很經(jīng)典的例子就是for循環(huán)中使用定時(shí)器延遲打印的問題:
for (var i = 1; i <= 10; i++) { setTimeout(function () { console.log(i); }, 1000); }
在這段代碼中,我們對(duì)其的預(yù)期是輸出1~10,但卻輸出10次11。這是因?yàn)閟etTimeout中的匿名函數(shù)執(zhí)行的時(shí)候,for循環(huán)都已經(jīng)結(jié)束了,for循環(huán)結(jié)束的條件是i大于10,所以當(dāng)然是輸出10次11咯。
究其原因:i是聲明在全局作用中的,定時(shí)器中的匿名函數(shù)也是執(zhí)行在全局作用域中,那當(dāng)然是每次都輸出11了。
原因知道了,解決起來就簡(jiǎn)單了,我們可以讓i在每次迭代的時(shí)候,都產(chǎn)生一個(gè)私有的作用域,在這個(gè)私有的作用域中保存當(dāng)前i的值。
for (var i = 1; i <= 10; i++) { (function () { var j = i; setTimeout(function () { console.log(j); }, 1000); })(); }
這樣就達(dá)到我們的預(yù)期了呀,讓我們用一種比較優(yōu)雅的寫法改造一些,將每次迭代的i作為實(shí)參傳遞給自執(zhí)行函數(shù),自執(zhí)行函數(shù)中用變量去接收:
for (var i = 1; i <= 10; i++) { (function (j) { setTimeout(function () { console.log(j); }, 1000); })(i); }閉包的應(yīng)用
閉包的應(yīng)用比較典型是定義模塊,我們將操作函數(shù)暴露給外部,而細(xì)節(jié)隱藏在模塊內(nèi)部:
function module() { var arr = []; function add(val) { if (typeof val == "number") { arr.push(val); } } function get(index) { if (index < arr.length) { return arr[index] } else { return null; } } return { add: add, get: get } } var mod1 = module(); mod1.add(1); mod1.add(2); mod1.add("xxx"); console.log(mod1.get(2));注
關(guān)于閉包還有很多要講,這里先講解比較基礎(chǔ)的概念,接下來還會(huì)有更精彩的內(nèi)容。
特別注意可以關(guān)注我的公眾號(hào):icemanFE,接下來持續(xù)更新技術(shù)文章!
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/95273.html
摘要:但閉包的情況不同嵌套函數(shù)的閉包執(zhí)行后,,然后還在被回收閉包會(huì)使變量始終保存在內(nèi)存中,如果不當(dāng)使用會(huì)增大內(nèi)存消耗。每個(gè)函數(shù),不論多深,都可以認(rèn)為是全局的子作用域,可以理解為閉包。 閉包(closure)是Javascript語言的一個(gè)難點(diǎn),也是它的特色,很多高級(jí)應(yīng)用都要依靠閉包實(shí)現(xiàn)。 閉包的特性 閉包有三個(gè)特性: 1.函數(shù)嵌套函數(shù) 2.函數(shù)內(nèi)部可以引用外部的參數(shù)和變量 3.參數(shù)和變量不會(huì)...
摘要:定義函數(shù)的時(shí)候,為什么的值重新從開始了因?yàn)橛忠淮芜\(yùn)行了函數(shù),生成一個(gè)新的的活動(dòng)對(duì)象,所以的作用域鏈引用的是一個(gè)新的值。 前言 在js中,閉包是一個(gè)很重要又相當(dāng)不容易完全理解的要點(diǎn),網(wǎng)上關(guān)于講解閉包的文章非常多,但是并不是非常容易讀懂,在這里以《javascript高級(jí)程序設(shè)計(jì)》里面的理論為基礎(chǔ)。用拆分的方式,深入講解一下對(duì)于閉包的理解,如果有不對(duì)請(qǐng)指正。 寫在閉包之前 閉包的內(nèi)部細(xì)節(jié),...
摘要:一般函數(shù)執(zhí)行完畢,局部活動(dòng)對(duì)象就會(huì)被銷毀,內(nèi)存中僅僅保存全局作用域,但是閉包會(huì)長(zhǎng)期駐扎在內(nèi)存。我只是想通過這兩個(gè)例子來說明閉包的用處和好處。閉包會(huì)使變量始終保存在內(nèi)存中,如果使用不當(dāng)會(huì)增大內(nèi)存消耗。 閉包特性 函數(shù)嵌套函數(shù) 函數(shù)內(nèi)部可以引用外部的參數(shù)和變量 參數(shù)和變量不會(huì)被垃圾回收機(jī)制回收 閉包的作用 具體作用是有權(quán)訪問函數(shù)內(nèi)部的變量,最常見的就是函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù),通過另一個(gè)函數(shù)...
摘要:環(huán)境由閉包創(chuàng)建時(shí)在作用域中的任何局部變量組成。嚴(yán)格來說,閉包需要滿足三個(gè)條件訪問所在作用域函數(shù)嵌套在所在作用域外被調(diào)用閉包的形成原理先了解的垃圾回收機(jī)制會(huì)找出不再使用的變量,不再使用意味著這個(gè)變量生命周期的結(jié)束。 什么是閉包 最原始定義 閉包(closure),是指函數(shù)變量可以保存在函數(shù)作用域內(nèi),因此看起來是函數(shù)將變量包裹了起來。 //根據(jù)定義,包含變量的函數(shù)就是閉包 function...
摘要:和數(shù)組遍歷方法詳解在中常用的種數(shù)組遍歷方法原始的循環(huán)語句數(shù)組對(duì)象內(nèi)置方法數(shù)組對(duì)象內(nèi)置方法數(shù)組對(duì)象內(nèi)置方法數(shù)組對(duì)象內(nèi)置方法數(shù)組對(duì)象內(nèi)置方法數(shù)組對(duì)象內(nèi)置方法數(shù)組對(duì)象內(nèi)置方法數(shù)組對(duì)象內(nèi)置方法循環(huán)語句中新增加了一種循環(huán)語句三種數(shù)組循環(huán)示例如下原始循 ES5和ES6數(shù)組遍歷方法詳解 在ES5中常用的10種數(shù)組遍歷方法: 1、原始的for循環(huán)語句2、Array.prototype.forEach數(shù)...
閱讀 2031·2021-11-11 16:55
閱讀 2310·2021-10-08 10:13
閱讀 861·2019-08-30 11:01
閱讀 2289·2019-08-29 13:19
閱讀 3365·2019-08-28 18:18
閱讀 2694·2019-08-26 13:26
閱讀 654·2019-08-26 11:40
閱讀 1958·2019-08-23 17:17