從入行就有人跟我說(shuō)閉包锣杂,到現(xiàn)在閉包也是前端躲不開(kāi)的概念,每次面試的時(shí)候我也會(huì)問(wèn)一下閉包相關(guān)的問(wèn)題番宁,今天針對(duì)閉包寫(xiě)一下我的理解
我想從閉包的表現(xiàn)形式元莫、閉包的作用和閉包的原理說(shuō)一下閉包
閉包的表現(xiàn)形式
閉包的表現(xiàn)形式就是一個(gè)函數(shù)中返回另一個(gè)函數(shù),返回的函數(shù)中又引用了該函數(shù)作用域內(nèi)的變量蝶押,另一種形式是函數(shù)作為參數(shù)傳遞也會(huì)行程閉包
function add(){
let a = 1
return function(b){
return a+b
}
}
const addfunc = add()
console.log(addfunc(10))
/* * @params n,m number型 * @return 返回兩個(gè)數(shù)相加額結(jié)果 */
function add(n,m){
return n+m;
} //閉包
(function fn(f){
var n = 1
var m = 2
f(n,m); // 調(diào)用add函數(shù)
})(add); // add函數(shù)作為參數(shù)f傳入
以上形式就是一個(gè)簡(jiǎn)單的閉包踱蠢,函數(shù)內(nèi)返回一個(gè)函數(shù),返回的函數(shù)調(diào)用的時(shí)候能夠獲取到該函數(shù)中的變量a的值播聪,至于為什么能夠獲取到a的值朽基,后面原理再詳細(xì)講解,這步只要知道閉包的表現(xiàn)形式就行了
閉包的作用
閉包的作用是封裝离陶,封裝的好處是可以避免全局污染和延長(zhǎng)變量的生命周期稼虎,如何避免全局污染和怎么延長(zhǎng)變量的使用周期的后面再講
function test(){
var a = 0;
var b = 1;
function getA(){
return a
}
function setA(value){
a = value
}
function add(){
return a+b
}
return {
getA:getA,
setA:setA,
add:add
}
}
const testItem = test()
testItem.setA(10)
console.log(testItem.getA())
console.log(testItem.add())
上面的代碼返回的對(duì)象中有函數(shù)使用到了test作用域內(nèi)的變量,那么這個(gè)作用域在函數(shù)執(zhí)行完畢之后就不會(huì)被清除招刨,你也可以理解成因?yàn)檫@塊作用域的變量在之后的代碼運(yùn)行中會(huì)被用到霎俩,所以這塊作用域在函數(shù)執(zhí)行完畢之后沒(méi)有被清除反而被鎖定了,也可以說(shuō)是封裝到一塊內(nèi)存中了,而且內(nèi)部的變量只能通過(guò)返回的函數(shù)去操作打却,無(wú)法通過(guò)外部去修改也不會(huì)受到外部環(huán)境的影響
閉包的原理
閉包的原理要從js的垃圾回收機(jī)制和作用域講起
作用域:在js中一個(gè)作用域是以函數(shù)的大括號(hào)為基本單位的杉适,每個(gè)函數(shù)在執(zhí)行的時(shí)候都會(huì)在內(nèi)存中創(chuàng)建一塊區(qū)域用來(lái)保存這個(gè)函數(shù)內(nèi)所創(chuàng)建的變量
function parent(){
var a = 1
console.log("父作用域parent")
function child(){
var b = 2
console.log("子作用域child")
console.log(a+b)
}
child()
}
function parent2(){
var c = 3
console.log("parent2作用域")
}
parent()
parent2()
上邊的代碼執(zhí)行的時(shí)候,內(nèi)存中創(chuàng)建了三塊作用域柳击,分別是parent猿推、child、和parent2捌肴,parent和child是嵌套的蹬叭,又叫父子作用域,兩個(gè)作用域形成了一個(gè)作用域鏈,用圖片描述如下
順便說(shuō)一下状知,變量值獲取會(huì)按照作用域鏈向上查詢秽五,child函數(shù)在執(zhí)行的時(shí)候在本作用域下沒(méi)有找到b變量就會(huì)去父作用域去找這個(gè)變量找到了就直接用,沒(méi)找到就在向上去找饥悴,但是在parent中是無(wú)法直接獲取到parent2中聲明的變量c的
作用域和上下文的區(qū)別:
作用域只是一個(gè)“地盤(pán)”坦喘,一個(gè)抽象的概念,其中沒(méi)有變量西设。
要通過(guò)作用域?qū)?yīng)的執(zhí)行上下文環(huán)境來(lái)獲取變量的值瓣铣。
同一個(gè)作用域下,不同的調(diào)用會(huì)產(chǎn)生不同的執(zhí)行上下文環(huán)境济榨,繼而產(chǎn)生不同的變量的值坯沪。
所以绿映,作用域中變量的值是在執(zhí)行過(guò)程中產(chǎn)生的確定的擒滑,而作用域卻是在函數(shù)創(chuàng)建時(shí)就確定了。
垃圾回收機(jī)制:每個(gè)函數(shù)在執(zhí)行的時(shí)候都會(huì)在內(nèi)存中創(chuàng)建一塊區(qū)域叉弦,一個(gè)函數(shù)在執(zhí)行之前js會(huì)把函數(shù)內(nèi)部變量存到內(nèi)存中丐一,然后再一行一行的執(zhí)行函數(shù)內(nèi)部的代碼,當(dāng)函數(shù)執(zhí)行完畢后淹冰,這塊內(nèi)存會(huì)被回收库车,就是刪除,這就是js的垃圾回收機(jī)制樱拴。
但是有一種情況是不會(huì)清除內(nèi)存的柠衍,就是閉包,就像文章最上邊的代碼add函數(shù)執(zhí)行完畢后返回一個(gè)函數(shù)晶乔,而這個(gè)函數(shù)用到了add作用域內(nèi)的a變量珍坊,add雖然執(zhí)行完畢了,但是如果這個(gè)時(shí)候清除內(nèi)存那么當(dāng)調(diào)用addfunc的時(shí)候他用到的a變量就獲取不到了正罢,所以js會(huì)在內(nèi)存中保留這塊區(qū)域阵漏,這也就是閉包可以延長(zhǎng)變量聲明周期的原因
舉一個(gè)遇到閉包就會(huì)提到的例子:
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
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();
如上代碼,在執(zhí)行的時(shí)候我們希望點(diǎn)擊p標(biāo)簽的時(shí)候分別彈出相應(yīng)的help,但是實(shí)際情況是點(diǎn)擊p標(biāo)簽彈出的都是'Your age (you must be over 16)'
之前提過(guò)作用域是以函數(shù)的大括號(hào)為單位的,所以上邊的代碼在執(zhí)行setupHelp函數(shù)的時(shí)候內(nèi)存中創(chuàng)建了一塊區(qū)域如下
input獲取焦點(diǎn)時(shí)執(zhí)行函數(shù)showHelp履怯,showHelp中使用item的help值回还,為什么會(huì)每次help值都是一樣的,問(wèn)題就出現(xiàn)在item變量創(chuàng)建的位置叹洲,循環(huán)三次只不過(guò)是給item附值三次柠硕,最后的item賦的值是helpText[2],所以每次獲取焦點(diǎn)時(shí)到內(nèi)存中獲取到的都是helpText[2].help运提,這個(gè)時(shí)候就應(yīng)該使用閉包的形式解決仅叫,如下
function showHelp(help) {
document.getElementById('help').innerHTML = help;
}
function makeHelpCallback(help) {
return function() {
showHelp(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 = makeHelpCallback(item.help);
}
}
setupHelp();
這樣在內(nèi)存中的形式如下
循環(huán)的時(shí)候每次都執(zhí)行了一次makeHelpCallback函數(shù),每執(zhí)行一次函數(shù)就會(huì)在內(nèi)存中創(chuàng)建一塊區(qū)域糙捺,這個(gè)區(qū)域執(zhí)行完之后返回的函數(shù)使用到了helpText的數(shù)據(jù)诫咱,這個(gè)就形成了閉包,所以這塊區(qū)域會(huì)被保存下來(lái)等待執(zhí)行時(shí)獲取變量的值洪灯,這樣每個(gè)區(qū)域存的內(nèi)容就是循環(huán)的時(shí)候當(dāng)前item的值坎缭。input獲取焦點(diǎn)的時(shí)候就會(huì)到相應(yīng)的作用域內(nèi)去獲取item.help。
ES6新出的聲明變量的方式let也可以解決上述問(wèn)題签钩,是因?yàn)閘et聲明的變量只在他坐在的大括號(hào)內(nèi)有效掏呼,無(wú)論是函數(shù)的大括號(hào)還是for循環(huán)if判斷的大括號(hào)。
<p id="help">Helpful notes will appear here</p>
<p>E-mail: <input type="text" id="email" name="email"></p>
<p>Name: <input type="text" id="name" name="name"></p>
<p>Age: <input type="text" id="age" name="age"></p>
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++) {
let item = helpText[i];
document.getElementById(item.id).onfocus = function() {
showHelp(item.help);
}
}
}
setupHelp();
當(dāng)然閉包也有缺點(diǎn)铅檩,因?yàn)樾纬砷]包之后垃圾回收機(jī)制無(wú)法釋放內(nèi)存憎夷,所以大量使用閉包有造成內(nèi)存溢出的風(fēng)險(xiǎn)。
以上就是我理解的js閉包昧旨,如果感興趣的朋友可以到官網(wǎng)去詳細(xì)的閱讀一下官網(wǎng)的講解