到底什么是闭包
一開(kāi)始接觸閉包有些問(wèn)題一直繞不過(guò)去,可了看其他的資料,也從網(wǎng)上查了查,下面是我總結(jié)的一些東西:
“官方”的解釋是:所謂“閉包”,指的是一個(gè)擁有許多變量和綁定了這些變量的環(huán)境的表達(dá)式(通常是一個(gè)函數(shù)),因而這些變量也是該表達(dá)式的一部分。
通俗的講:就是函數(shù)a的內(nèi)部函數(shù)b,被函數(shù)a外部的一個(gè)變量引用的時(shí)候,就創(chuàng)建了一個(gè)閉包。
(這樣在執(zhí)行完var c=a()后,變量c實(shí)際上是指向了函數(shù)b,再執(zhí)行c()后就會(huì)彈出一個(gè)窗口顯示i的值(第一次為1)。這段代碼其實(shí)就創(chuàng)建了一個(gè)閉包,為什么?因?yàn)楹瘮?shù)a外的變量c引用了函數(shù)a內(nèi)的函數(shù)b)
function a(){var i=0;function b(){alert(++i);}return b;}var c = a();c();閉包的特性:
①.封閉性:外界無(wú)法訪問(wèn)閉包內(nèi)部的數(shù)據(jù),如果在閉包內(nèi)聲明變量,外界是無(wú)法訪問(wèn)的,除非閉包主動(dòng)向外界提供訪問(wèn)接口;
②.持久性:一般的函數(shù),調(diào)用完畢之后,系統(tǒng)自動(dòng)注銷(xiāo)函數(shù),而對(duì)于閉包來(lái)說(shuō),在外部函數(shù)被調(diào)用之后,閉包結(jié)構(gòu)依然保存在
系統(tǒng)中,閉包中的數(shù)據(jù)依然存在,從而實(shí)現(xiàn)對(duì)數(shù)據(jù)的持久使用。
優(yōu)點(diǎn):
①?減少全局變量。
②?減少傳遞函數(shù)的參數(shù)量
③?封裝;
?缺點(diǎn):
?使用閉包會(huì)占有內(nèi)存資源,過(guò)多的使用閉包會(huì)導(dǎo)致內(nèi)存溢出等.
?
最簡(jiǎn)潔、直擊要害的回答,我能想到的分別有這么三句
1、閉包是一個(gè)有狀態(tài)(不消失的私有數(shù)據(jù))的函數(shù)。
2、閉包是一個(gè)有記憶的函數(shù)。
3、閉包相當(dāng)于一個(gè)只有一個(gè)方法的緊湊對(duì)象(a compact object)。
上面這三句話是等價(jià)的,而其中第 3 句最精妙,可以指導(dǎo)何時(shí)、如何用好閉包。
我換個(gè)角度來(lái)談?wù)劇?br />
首先,很明確——閉包是一個(gè)函數(shù),一種比較特殊的函數(shù)。什么是函數(shù)?函數(shù)就是一個(gè)基本的程序運(yùn)行邏輯單位(模塊),通常有一組輸入,有一個(gè)輸出結(jié)果,內(nèi)部還有一些進(jìn)行運(yùn)算的程序語(yǔ)句。所以,那些僅僅說(shuō)閉包是作用域(scopes)或者其它什么的,是錯(cuò)誤的,至少不準(zhǔn)確。
理解了以上這些概念,關(guān)于“什么是閉包”您的大腦中是否出現(xiàn)了下面這張圖(用 UML 組成結(jié)構(gòu)圖
有實(shí)例有真相
讓我們先回顧下傳統(tǒng)函式的機(jī)理。?
我們說(shuō)普通函式自身是沒(méi)有狀態(tài)的(stateless),它們所使用的局部變量都是保存在函式調(diào)用棧(Stack)上,隨著函式調(diào)用的結(jié)束、退出,這些臨時(shí)保存在棧上的變量也就被清空了,所以普通函式是沒(méi)有狀態(tài)、沒(méi)有記憶的。
例如下面的普通函式 inc(),不管執(zhí)行多少次都只返回 1:
?
var inc = function () {var count = 0;return ++count; };inc(); // return: 1 inc(); // return: 1?
為什么這樣?這是因?yàn)檫@里的 count 只是一個(gè)普通函式的局部變量,每次執(zhí)行函式時(shí)都會(huì)被重新初始化(被第一條語(yǔ)句清零),它不是下面例子中可以保持狀態(tài)的閉包變量。
再來(lái)看閉包的例子。
這可以說(shuō)是一個(gè)最簡(jiǎn)單的 JavaScript 閉包的例子,這里的 inc() 是一個(gè)閉包(函式),它有一個(gè)私有數(shù)據(jù)(也叫閉包變量) count(即函式中的第 2 個(gè) count)。
var inc = (function () { // 該函數(shù)體中的語(yǔ)句將被立即執(zhí)行(IIFE)var count = 0; // 局部變量 count 初始化return function () { // 父函式返回一個(gè)閉包(函式引用)return ++count; // 當(dāng)父函式 return(即上一個(gè) return)后,這里的 count 不再是父函式的局部變量,而是返回結(jié)果閉包中的一個(gè)閉包(環(huán)境)變量。 }; }) ();inc(); // return: 1 inc(); // return: 2我還未研究過(guò)任何 JavaScript 引擎(解釋器)的源碼,所以只好根據(jù)常識(shí)與邏輯作些合理的推測(cè)。?
在本例中第 2 個(gè) count 作為閉包的私有數(shù)據(jù),很可能是被 JS 引擎存放到了堆(Heap)上,而且是按引用(byref)來(lái)訪問(wèn),所以可以保持狀態(tài),實(shí)現(xiàn)計(jì)數(shù)累加;而第 1 個(gè) count 只是存放在函式調(diào)用棧(Stack)上的局部變量,于是那個(gè) IIFE 父函式一退出它就被銷(xiāo)毀了,它的作用主要是用來(lái)初始化(賦值)給擔(dān)任閉包變量的第 2 個(gè) count。
可見(jiàn)兩個(gè) count 雖然同名,卻是兩個(gè)截然不同的變量!
這點(diǎn)恐怕正是許多 JS 初學(xué)者(包括當(dāng)年的我)屢屢見(jiàn)到閉包時(shí),感到最為大惑不解的地方吧。我們以為父子函式里外兩個(gè)同名的變量是一回事,而其實(shí)它們不是,也不知道這背后究竟發(fā)生了哪些變化。
?
閉包 vs. 對(duì)象?
實(shí)現(xiàn)同樣的計(jì)數(shù)功能,不用閉包怎么寫(xiě)?同樣以 JavaScript 為例,用傳統(tǒng)的 OOP 來(lái)寫(xiě):
用閉包與用對(duì)象,區(qū)別在哪?
其實(shí)主要區(qū)別就一個(gè):這里用的是普通對(duì)象 obj 的方法(函數(shù))inc,讓 count 作為 obj 的成員變量來(lái)保存數(shù)據(jù)。而前面第一個(gè)例子直接用閉包函數(shù) inc 的話,連 obj 這個(gè)對(duì)象也可以省掉,讓 count 直接成為 inc 閉包內(nèi)部所保存的狀態(tài)(環(huán)境)變量,這樣寫(xiě)起來(lái)就比傳統(tǒng)的 OOP 更為緊湊,前者用 inc(),而后者用 obj.inc(),盡管兩者最終實(shí)現(xiàn)的功能和效果基本是一致的。
通過(guò)以上這兩個(gè)小例子的比較,你可以充分體會(huì)到在 JavaScript 中,函數(shù)(functions)作為首席/頭等公民(first-class object)的地位。由于有了閉包,加上在 JavaScript 中函數(shù)也是對(duì)象——一個(gè)函數(shù)可以像一個(gè)傳統(tǒng)的對(duì)象那樣擁有自己的屬性、私有數(shù)據(jù)和狀態(tài)(不會(huì)隨著棧而清空),許多簡(jiǎn)單功能的實(shí)現(xiàn)可能無(wú)需再借助 objects 了。
最近有遇到一個(gè)這個(gè)的面試題,也是有關(guān)于閉包方面,很有意思,大家可以一起來(lái)看一下:
試題的要求是:遍歷ul下的li,點(diǎn)擊彈出li的索引
首先我們需要一個(gè)html結(jié)構(gòu)
<div ><ul><li>a</li><li>a</li><li>a</li><li>a</li><li>a</li></ul></div>?我們遍歷ul 下所有的li 并添加點(diǎn)擊事件,一般我們會(huì)在for循環(huán)里面添加點(diǎn)擊事件,但是結(jié)果和我們所期盼不一樣,那么是為什么呢????
var nodes = document.getElementsByTagName("li");for( var i = 0; i<nodes.length; i++ ){nodes[i].onclick = function(){console.log(i); // 4 }}接下來(lái)看看我們的js代碼
var nodes = document.getElementsByTagName("li");for( var i = 0; i<nodes.length; i++ ){(function(index){nodes[index].onclick = function(){console.log(index);}})(i)}我們實(shí)現(xiàn)了!!!
這樣就是得來(lái)我們想要的效果點(diǎn)擊相應(yīng)的li得來(lái)相應(yīng)的索引。
簡(jiǎn)單說(shuō)一下實(shí)現(xiàn)的過(guò)程吧
(function () { /* code */ } ()); // 推薦使用這個(gè)
(function () { /* code */ })(); // 但是這個(gè)也是可以用的
這是我整理立調(diào)函數(shù)或自執(zhí)行函數(shù);
本質(zhì)上我們是利用閉包的原理實(shí)現(xiàn)彈出的索引,我們立調(diào)函數(shù)傳一個(gè)參數(shù)Index,也就是我們的索引i,在函數(shù)里面實(shí)現(xiàn)了閉包,
Index會(huì)一直保留在作用域塊內(nèi),這樣我們?cè)冱c(diǎn)擊的時(shí)候,會(huì)調(diào)用作用域名內(nèi)保存的索引,從而實(shí)現(xiàn)我們需要的功能;
?
我們幾個(gè)簡(jiǎn)單的例子:
function num(){var i = 0;return function(){console.log(i++);} } var counter = num(); console.log(counter()); // 0 console.log(counter()); // ?? var counter1 = (function(){var i = 0;return {get:function(){return i;},set:function(val){i = val;},increment:function(){return ++i;}}}());console.log(counter1);console.log(counter1.get()); // ?console.log(counter1.set(3)); // ?console.log(counter1.increment()); // ?console.log(counter1.increment()); // ?大家可以去思考一下答案哦!
轉(zhuǎn)載于:https://www.cnblogs.com/goodluck-tang/p/10414460.html
總結(jié)
- 上一篇: JavaScript(数据类型、字符串操
- 下一篇: Linux嗅探ettercap