javascript中閉包c(diǎn)losure的深入講解
簡(jiǎn)介
閉包c(diǎn)losure是javascript中一個(gè)非常強(qiáng)大的功能。所謂閉包就是函數(shù)中的函數(shù),內(nèi)部函數(shù)可以訪問(wèn)外部函數(shù)的作用域范圍,從而可以使用閉包來(lái)做一些比較強(qiáng)大的工作。
今天將會(huì)給大家詳細(xì)介紹一下閉包。
函數(shù)中的函數(shù)
我們提到了函數(shù)中的函數(shù)可以訪問(wèn)父函數(shù)作用域范圍的變量,我們看一個(gè)例子:
function parentFunction() {
var address = 'flydean.com';
function alertAddress() {
alert(address);
}
alertAddress();
}
parentFunction();
上面的例子中,我們?cè)趐arentFunction中定義了一個(gè)變量address,在parentFunction內(nèi)部定義了一個(gè)alertAddress方法,在該方法內(nèi)部訪問(wèn)外部函數(shù)中定義的address變量。
上面代碼運(yùn)行是沒(méi)問(wèn)題的,可以正確的訪問(wèn)到數(shù)據(jù)。
Closure閉包
函數(shù)中的函數(shù)有了,那么什么是閉包呢?
我們看下面的例子:
function parentFunction() {
var address = 'flydean.com';
function alertAddress() {
alert(address);
}
return alertAddress;
}
var myFunc = parentFunction();
myFunc();
這個(gè)例子和第一個(gè)例子很類似,不同之處就是我們將內(nèi)部函數(shù)返回了,并且賦值給了myFunc。
接下來(lái)我們直接調(diào)用了myFunc。
myFunc中訪問(wèn)了parentFunction中的address變量,雖然parentFunction已經(jīng)執(zhí)行完畢返回。
但是我們?cè)谡{(diào)用myFunc的時(shí)候,任然可以訪問(wèn)到address變量。這就是閉包。
閉包的這個(gè)特性非常擁有,我們可以使用閉包來(lái)生成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這個(gè)function factory創(chuàng)建出來(lái)的。通過(guò)傳遞不同的x參數(shù),我們得到了不同的基數(shù)的add方法。
最終生成了兩個(gè)不同的add方法。
使用function factory的概念,我們可以考慮一個(gè)閉包的實(shí)際應(yīng)用,比如我們?cè)陧?yè)面上有三個(gè)button,通過(guò)點(diǎn)擊這些button可實(shí)現(xiàn)修改字體的功能。
我們可以先通過(guò)function factory來(lái)生成三個(gè)方法:
function makeSizer(size) {
return function() {
document.body.style.fontSize = size + 'px';
};
}
var size12 = makeSizer(12);
var size14 = makeSizer(14);
var size16 = makeSizer(16);
有了這三個(gè)方法,我們把DOM元素和callback方法綁定起來(lái):
document.getElementById('size-12').onclick = size12;
document.getElementById('size-14').onclick = size14;
document.getElementById('size-16').onclick = size16;
使用閉包實(shí)現(xiàn)private方法
對(duì)比java來(lái)說(shuō),java中有private訪問(wèn)描述符,通過(guò)private,我們可以指定方法只在class內(nèi)部訪問(wèn)。
當(dāng)然,在JS中并沒(méi)有這個(gè)東西,但是我們可以使用閉包來(lái)達(dá)到同樣的效果。
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.
我們?cè)诟竑unction中定義了privateCounter屬性和changeBy方法,但是這些方法只能夠在內(nèi)部function中訪問(wèn)。
我們通過(guò)閉包的概念,將這些屬性和方法封裝起來(lái),暴露給外部使用,最終達(dá)到了私有變量和方法封裝的效果。
閉包的Scope Chain
對(duì)于每個(gè)閉包來(lái)說(shuō),都有一個(gè)作用域范圍,包括函數(shù)本身的作用域,父函數(shù)的作用域和全局的作用域。
如果我們?cè)诤瘮?shù)內(nèi)部嵌入了新的函數(shù),那么就會(huì)形成一個(gè)作用域鏈,我們叫做scope chain。
看下面的一個(gè)例子:
// 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
閉包常見(jiàn)的問(wèn)題
第一個(gè)常見(jiàn)的問(wèn)題就是在循環(huán)遍歷中使用閉包,我們看一個(gè)例子:
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)建了一個(gè)setupHelp函數(shù),setupHelp中,onfocus方法被賦予了一個(gè)閉包,所以閉包中的item可以訪問(wèn)到外部function中定義的item變量。
因?yàn)樵谘h(huán)里面賦值,所以我們實(shí)際上創(chuàng)建了3個(gè)閉包,但是這3個(gè)閉包共享的是同一個(gè)外部函數(shù)的作用域范圍。
我們的本意是,不同的id觸發(fā)不同的help消息。但是如果我們真正執(zhí)行就會(huì)發(fā)現(xiàn),不管是哪一個(gè)id,最終的消息都是最后一個(gè)。
因?yàn)閛nfocus是在閉包創(chuàng)建完畢之后才會(huì)觸發(fā),這個(gè)時(shí)候item的值實(shí)際上是變化的,在循環(huán)結(jié)束之后,item的值已經(jīng)指向了最后一個(gè)元素,所以全部顯示的是最后一條數(shù)據(jù)的help消息。
怎么解決這個(gè)問(wèn)題呢?
最簡(jiǎn)單的辦法使用ES6中引入的let描述符,從而將item定義為block的作用域范圍,每次循環(huán)都會(huì)創(chuàng)建一個(gè)新的item,從而保持閉包中的item的值不變。
for (let i = 0; i < helpText.length; i++) {
let item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
還有一種方法,就是再創(chuàng)建一個(gè)閉包:
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包含在一個(gè)新的function作用域范圍之內(nèi),從而每次創(chuàng)建都是新的item,這個(gè)和let的原理是相似的:
for (var i = 0; i < helpText.length; i++) {
(function() {
var item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
})();
}
第二個(gè)常見(jiàn)的問(wèn)題就是內(nèi)存泄露。
function parentFunction(paramA)
{
var a = paramA;
function childFunction()
{
return a + 2;
}
return childFunction();
}
上面的例子中,childFunction引用了parentFunction的變量a。只要childFunction還在被使用,a就無(wú)法被釋放,從而導(dǎo)致parentFunction無(wú)法被垃圾回收。
閉包性能的問(wèn)題
我們定義了一個(gè)對(duì)象,并且通過(guò)閉包來(lái)訪問(wèn)其私有屬性:
function MyObject(name, message) {
this.name = name.toString();
this.message = message.toString();
this.getName = function() {
return this.name;
};
this.getMessage = function() {
return this.message;
};
}
上面的對(duì)象會(huì)有什么問(wèn)題呢?
上面對(duì)象的問(wèn)題就在于,對(duì)于每一個(gè)new出來(lái)的對(duì)象,getName和getMessage方法都會(huì)被復(fù)制一份,一方面是內(nèi)容的冗余,另一方面是性能的影響。
通常來(lái)說(shuō),我們將對(duì)象的方法定義在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;
};
注意,我們不要直接重寫(xiě)整個(gè)prototype,這樣會(huì)導(dǎo)致未知的錯(cuò)誤,我們只需要根據(jù)需要重寫(xiě)特定的方法即可。
總結(jié)
閉包是JS中非常強(qiáng)大和有用的概念,希望大家能夠喜歡。
到此這篇關(guān)于javascript中閉包c(diǎn)losure的文章就介紹到這了,更多相關(guān)javascript閉包c(diǎn)losure內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
javascript createElement()創(chuàng)建input不能設(shè)置name屬性的解決方法
今天在使用document.createElement()動(dòng)態(tài)創(chuàng)建input時(shí),發(fā)現(xiàn)不能為其name賦值,以下是測(cè)試代碼。2009-10-10
js 實(shí)現(xiàn)數(shù)值的千分位及保存小數(shù)方法(推薦)
下面小編就為大家?guī)?lái)一篇js 實(shí)現(xiàn)數(shù)值的千分位及保存小數(shù)方法(推薦)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-08-08
document.createElement("A")比較不錯(cuò)的屬性
document.createElement("A")比較不錯(cuò)的屬性...2007-08-08
javascript 文字上下間隔滾動(dòng)的代碼 符合WEB標(biāo)準(zhǔn) 腳本之家修正版
javascript 文字上下間隔滾動(dòng)的代碼 符合WEB標(biāo)準(zhǔn) 腳本之家修正版,這里提供了兩個(gè)版本,第二個(gè)在firefox下運(yùn)行有些問(wèn)題大家可以修改下,第一個(gè)的高度問(wèn)題,已經(jīng)修正,其實(shí)就是簡(jiǎn)單的加了css樣式。2009-12-12
實(shí)例詳解JavaScript中setTimeout函數(shù)的執(zhí)行順序
關(guān)于javascript的運(yùn)行機(jī)制大家都應(yīng)該有所了解了吧,其實(shí)javascript是一個(gè)單線程的機(jī)制,但是因?yàn)殛?duì)列的關(guān)系它的表現(xiàn)會(huì)讓我們感覺(jué)是一個(gè)多線程的錯(cuò)覺(jué)。下面這篇文章通過(guò)實(shí)例主要給大家介紹了關(guān)于JavaScript中setTimeout函數(shù)執(zhí)行順序的相關(guān)資料,需要的朋友可以參考下。2017-07-07
在頁(yè)面中輸出當(dāng)前客戶端時(shí)間javascript實(shí)例代碼
這篇文章主要介紹了在頁(yè)面中輸出當(dāng)前客戶端時(shí)間javascript實(shí)例代碼的相關(guān)資料,需要的朋友可以參考下2016-03-03
JS 組件系列之Bootstrap Table 凍結(jié)列功能IE瀏覽器兼容性問(wèn)題解決方案
這篇文章主要介紹了JS 組件系列之Bootstrap Table 凍結(jié)列功能IE瀏覽器兼容性問(wèn)題解決方案,需要的朋友可以參考下2017-06-06

