簡介
閉包closure是javascript中一個非常強大的功能。所謂閉包就是函數(shù)中的函數(shù),內(nèi)部函數(shù)可以訪問外部函數(shù)的作用域范圍,從而可以使用閉包來做一些比較強大的工作。
今天將會給大家詳細介紹一下閉包。
函數(shù)中的函數(shù)
我們提到了函數(shù)中的函數(shù)可以訪問父函數(shù)作用域范圍的變量,我們看一個例子:
1
2
3
4
5
6
7
8
|
function parentFunction() { var address = 'flydean.com' ; function alertAddress() { alert(address); } alertAddress(); } parentFunction(); |
上面的例子中,我們在parentFunction中定義了一個變量address,在parentFunction內(nèi)部定義了一個alertAddress方法,在該方法內(nèi)部訪問外部函數(shù)中定義的address變量。
上面代碼運行是沒問題的,可以正確的訪問到數(shù)據(jù)。
Closure閉包
函數(shù)中的函數(shù)有了,那么什么是閉包呢?
我們看下面的例子:
1
2
3
4
5
6
7
8
9
|
function parentFunction() { var address = 'flydean.com' ; function alertAddress() { alert(address); } return alertAddress; } var myFunc = parentFunction(); myFunc(); |
這個例子和第一個例子很類似,不同之處就是我們將內(nèi)部函數(shù)返回了,并且賦值給了myFunc。
接下來我們直接調(diào)用了myFunc。
myFunc中訪問了parentFunction中的address變量,雖然parentFunction已經(jīng)執(zhí)行完畢返回。
但是我們在調(diào)用myFunc的時候,任然可以訪問到address變量。這就是閉包。
閉包的這個特性非常擁有,我們可以使用閉包來生成function factory,如下所示:
1
2
3
4
5
6
7
8
9
10
11
|
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創(chuàng)建出來的。通過傳遞不同的x參數(shù),我們得到了不同的基數(shù)的add方法。
最終生成了兩個不同的add方法。
使用function factory的概念,我們可以考慮一個閉包的實際應(yīng)用,比如我們在頁面上有三個button,通過點擊這些button可實現(xiàn)修改字體的功能。
我們可以先通過function factory來生成三個方法:
1
2
3
4
5
6
7
8
9
|
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方法綁定起來:
1
2
3
|
document.getElementById( 'size-12' ).onclick = size12; document.getElementById( 'size-14' ).onclick = size14; document.getElementById( 'size-16' ).onclick = size16; |
使用閉包實現(xiàn)private方法
對比java來說,java中有private訪問描述符,通過private,我們可以指定方法只在class內(nèi)部訪問。
當然,在JS中并沒有這個東西,但是我們可以使用閉包來達到同樣的效果。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
|
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方法,但是這些方法只能夠在內(nèi)部function中訪問。
我們通過閉包的概念,將這些屬性和方法封裝起來,暴露給外部使用,最終達到了私有變量和方法封裝的效果。
閉包的Scope Chain
對于每個閉包來說,都有一個作用域范圍,包括函數(shù)本身的作用域,父函數(shù)的作用域和全局的作用域。
如果我們在函數(shù)內(nèi)部嵌入了新的函數(shù),那么就會形成一個作用域鏈,我們叫做scope chain。
看下面的一個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 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 |
閉包常見的問題
第一個常見的問題就是在循環(huán)遍歷中使用閉包,我們看一個例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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(); |
上面的例子中,我們創(chuàng)建了一個setupHelp函數(shù),setupHelp中,onfocus方法被賦予了一個閉包,所以閉包中的item可以訪問到外部function中定義的item變量。
因為在循環(huán)里面賦值,所以我們實際上創(chuàng)建了3個閉包,但是這3個閉包共享的是同一個外部函數(shù)的作用域范圍。
我們的本意是,不同的id觸發(fā)不同的help消息。但是如果我們真正執(zhí)行就會發(fā)現(xiàn),不管是哪一個id,最終的消息都是最后一個。
因為onfocus是在閉包創(chuàng)建完畢之后才會觸發(fā),這個時候item的值實際上是變化的,在循環(huán)結(jié)束之后,item的值已經(jīng)指向了最后一個元素,所以全部顯示的是最后一條數(shù)據(jù)的help消息。
怎么解決這個問題呢?
最簡單的辦法使用ES6中引入的let描述符,從而將item定義為block的作用域范圍,每次循環(huán)都會創(chuàng)建一個新的item,從而保持閉包中的item的值不變。
1
2
3
4
5
6
|
for (let i = 0; i < helpText.length; i++) { let item = helpText[i]; document.getElementById(item.id).onfocus = function () { showHelp(item.help); } } |
還有一種方法,就是再創(chuàng)建一個閉包:
1
2
3
4
5
6
7
8
9
10
|
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的概念,我們?yōu)椴煌拈]包創(chuàng)建了不同的作用域環(huán)境。
還有一種方法就是將item包含在一個新的function作用域范圍之內(nèi),從而每次創(chuàng)建都是新的item,這個和let的原理是相似的:
1
2
3
4
5
6
7
8
|
for ( var i = 0; i < helpText.length; i++) { ( function () { var item = helpText[i]; document.getElementById(item.id).onfocus = function () { showHelp(item.help); } })(); } |
第二個常見的問題就是內(nèi)存泄露。
1
2
3
4
5
6
7
8
9
|
function parentFunction(paramA) { var a = paramA; function childFunction() { return a + 2; } return childFunction(); } |
上面的例子中,childFunction引用了parentFunction的變量a。只要childFunction還在被使用,a就無法被釋放,從而導(dǎo)致parentFunction無法被垃圾回收。
閉包性能的問題
我們定義了一個對象,并且通過閉包來訪問其私有屬性:
1
2
3
4
5
6
7
8
9
10
11
|
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方法都會被復(fù)制一份,一方面是內(nèi)容的冗余,另一方面是性能的影響。
通常來說,我們將對象的方法定義在prototype上面:
1
2
3
4
5
6
7
8
9
10
|
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,這樣會導(dǎo)致未知的錯誤,我們只需要根據(jù)需要重寫特定的方法即可。
總結(jié)
閉包是JS中非常強大和有用的概念,希望大家能夠喜歡。
到此這篇關(guān)于javascript中閉包closure的文章就介紹到這了,更多相關(guān)javascript閉包closure內(nèi)容請搜索服務(wù)器之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持服務(wù)器之家!
原文鏈接:https://www.cnblogs.com/flydean/p/14470911.html