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