閉包closure是javascript中一個很是強大的功能。所謂閉包就是函數中的函數,內部函數能夠訪問外部函數的做用域範圍,從而可使用閉包來作一些比較強大的工做。javascript
今天將會給你們詳細介紹一下閉包。java
咱們提到了函數中的函數能夠訪問父函數做用域範圍的變量,咱們看一個例子:閉包
function parentFunction() { var address = 'flydean.com'; function alertAddress() { alert(address); } alertAddress(); } parentFunction();
上面的例子中,咱們在parentFunction中定義了一個變量address,在parentFunction內部定義了一個alertAddress方法,在該方法內部訪問外部函數中定義的address變量。函數
上面代碼運行是沒問題的,能夠正確的訪問到數據。性能
函數中的函數有了,那麼什麼是閉包呢?字體
咱們看下面的例子:this
function parentFunction() { var address = 'flydean.com'; function alertAddress() { alert(address); } return alertAddress; } var myFunc = parentFunction(); myFunc();
這個例子和第一個例子很相似,不一樣之處就是咱們將內部函數返回了,而且賦值給了myFunc。prototype
接下來咱們直接調用了myFunc。code
myFunc中訪問了parentFunction中的address變量,雖然parentFunction已經執行完畢返回。對象
可是咱們在調用myFunc的時候,任然能夠訪問到address變量。這就是閉包。
閉包的這個特性很是擁有,咱們可使用閉包來生成function factory,以下所示:
function makeAdder(x) { return function(y) { return x + y; }; } var add5 = makeAdder(5); var add10 = makeAdder(10); console.log(add5(2)); // 7 console.log(add10(2)); // 12
其中add5和add10都是閉包,他們是由makeAdder這個function factory建立出來的。經過傳遞不一樣的x參數,咱們獲得了不一樣的基數的add方法。
最終生成了兩個不一樣的add方法。
使用function factory的概念,咱們能夠考慮一個閉包的實際應用,好比咱們在頁面上有三個button,經過點擊這些button可實現修改字體的功能。
咱們能夠先經過function factory來生成三個方法:
function makeSizer(size) { return function() { document.body.style.fontSize = size + 'px'; }; } var size12 = makeSizer(12); var size14 = makeSizer(14); var size16 = makeSizer(16);
有了這三個方法,咱們把DOM元素和callback方法綁定起來:
document.getElementById('size-12').onclick = size12; document.getElementById('size-14').onclick = size14; document.getElementById('size-16').onclick = size16;
對比java來講,java中有private訪問描述符,經過private,咱們能夠指定方法只在class內部訪問。
固然,在JS中並無這個東西,可是咱們可使用閉包來達到一樣的效果。
var counter = (function() { var privateCounter = 0; function changeBy(val) { privateCounter += val; } return { increment: function() { changeBy(1); }, decrement: function() { changeBy(-1); }, value: function() { return privateCounter; } }; })(); console.log(counter.value()); // 0. counter.increment(); counter.increment(); console.log(counter.value()); // 2. counter.decrement(); console.log(counter.value()); // 1.
咱們在父function中定義了privateCounter屬性和changeBy方法,可是這些方法只可以在內部function中訪問。
咱們經過閉包的概念,將這些屬性和方法封裝起來,暴露給外部使用,最終達到了私有變量和方法封裝的效果。
對於每一個閉包來講,都有一個做用域範圍,包括函數自己的做用域,父函數的做用域和全局的做用域。
若是咱們在函數內部嵌入了新的函數,那麼就會造成一個做用域鏈,咱們叫作scope chain。
看下面的一個例子:
// global scope var e = 10; function sum(a){ return function(b){ return function(c){ // outer functions scope return function(d){ // local scope return a + b + c + d + e; } } } } console.log(sum(1)(2)(3)(4)); // log 20
第一個常見的問題就是在循環遍歷中使用閉包,咱們看一個例子:
function showHelp(help) { document.getElementById('help').innerHTML = help; } function setupHelp() { var helpText = [ {'id': 'email', 'help': 'Your e-mail address'}, {'id': 'name', 'help': 'Your full name'}, {'id': 'age', 'help': 'Your age (you must be over 16)'} ]; for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } } setupHelp();
上面的例子中,咱們建立了一個setupHelp函數,setupHelp中,onfocus方法被賦予了一個閉包,因此閉包中的item能夠訪問到外部function中定義的item變量。
由於在循環裏面賦值,因此咱們實際上建立了3個閉包,可是這3個閉包共享的是同一個外部函數的做用域範圍。
咱們的本意是,不一樣的id觸發不一樣的help消息。可是若是咱們真正執行就會發現,不論是哪個id,最終的消息都是最後一個。
由於onfocus是在閉包建立完畢以後纔會觸發,這個時候item的值其實是變化的,在循環結束以後,item的值已經指向了最後一個元素,因此所有顯示的是最後一條數據的help消息。
怎麼解決這個問題呢?
最簡單的辦法使用ES6中引入的let描述符,從而將item定義爲block的做用域範圍,每次循環都會建立一個新的item,從而保持閉包中的item的值不變。
for (let i = 0; i < helpText.length; i++) { let item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } }
還有一種方法,就是再建立一個閉包:
function makeHelpCallback(help) { return function() { showHelp(help); }; } for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = makeHelpCallback(item.help); }
這裏用到了以前咱們提到的function factory的概念,咱們爲不一樣的閉包建立了不一樣的做用域環境。
還有一種方法就是將item包含在一個新的function做用域範圍以內,從而每次建立都是新的item,這個和let的原理是類似的:
for (var i = 0; i < helpText.length; i++) { (function() { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } })(); }
第二個常見的問題就是內存泄露。
function parentFunction(paramA) { var a = paramA; function childFunction() { return a + 2; } return childFunction(); }
上面的例子中,childFunction引用了parentFunction的變量a。只要childFunction還在被使用,a就沒法被釋放,從而致使parentFunction沒法被垃圾回收。
咱們定義了一個對象,而且經過閉包來訪問其私有屬性:
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; }
上面的對象會有什麼問題呢?
上面對象的問題就在於,對於每個new出來的對象,getName和getMessage方法都會被複制一份,一方面是內容的冗餘,另外一方面是性能的影響。
一般來講,咱們將對象的方法定義在prototype上面:
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype.getName = function() { return this.name; }; MyObject.prototype.getMessage = function() { return this.message; };
注意,咱們不要直接重寫整個prototype,這樣會致使未知的錯誤,咱們只須要根據須要重寫特定的方法便可。
閉包是JS中很是強大和有用的概念,但願你們可以喜歡。
本文做者:flydean程序那些事本文連接:http://www.flydean.com/js-closure/
本文來源:flydean的博客
歡迎關注個人公衆號:「程序那些事」最通俗的解讀,最深入的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!