在學(xué)習(xí)js的過程中,我們都會遇到閉包和立即執(zhí)行函數(shù)的相關(guān)概念,今天就這兩個概念做一個大致的整理贩毕。
本文結(jié)構(gòu):
-
閉包
- 閉包的概念
-
立即執(zhí)行函數(shù)
- 什么是立即執(zhí)行函數(shù)
- 一個經(jīng)典面試題
- es6 中的 let
一些己、閉包
我們先來看閉包的作用:閉包通常用來創(chuàng)建內(nèi)部變量,使得這些變量不能被外部隨意修改潜必,同時又可以通過指定的函數(shù)接口來操作。
對于不理解閉包的朋友來說沃但,上面這句話并沒有什么用磁滚。所以我們通過一個簡單的例子來理解這個概念。
比如說我們現(xiàn)在要寫一個游戲宵晚,每個人初始都有5點精力垂攘,贏了就可以加1點精力,輸了就要減1點精力淤刃。
var amount=5; //初始有5點精力
function add(){ //增加精力
amount+=1;
return amount;
}
function minus(){ //減少精力
amount-=1;
return amount;
}
//第一局贏了晒他,調(diào)用增加精力函數(shù)
add();console.log(amount); //結(jié)果為6
//第二局輸了,調(diào)用減少精力函數(shù)
minus();console.log(amount); //結(jié)果為5
這樣就初步實現(xiàn)了我們想要的效果钝凶。
但是仪芒,這種做法存在這樣一個問題: 由于amount暴露在全局作用域下唁影,是一個全局變量,所以任何人都可以隨意修改我的精力值掂名。如果我們不小心在另一個js文件里也聲明了amount這樣一個全局變量并給它賦值据沈,那么我們這里的amount就可能被覆蓋。
我們當(dāng)前不希望amount被覆蓋饺蔑,我們希望amount能“私有”一些锌介,因此,我們對amount提出了兩點要求:
- 它不能被外部隨意修改猾警;
- 需要能通過指定的方式去操作它孔祸。
首先,我們把上述代碼包在一個函數(shù)里:
function xxx(){
var amount=5;
function add(){
amount+=1;
return amount;
}
function minus(){
amount-=1;
return amount;
}
}
這樣发皿,amount的作用域就變?yōu)榱撕瘮?shù)xxx的內(nèi)部崔慧,在函數(shù)外面無法訪問,這樣就實現(xiàn)了“不能被外部隨意修改”第一個要求穴墅。
但是惶室,在函數(shù)xxx的外部,我們完全操作不到amount玄货。因此皇钞,我們還需要給函數(shù)xxx留一個接口,讓我們能夠在函數(shù)外部去操作它松捉。這該怎么辦呢夹界?
經(jīng)過一番苦思冥想,我們終于想到隘世,是否可以利用函數(shù)的返回值呢可柿?
function xxx(){
var amount=5;
function add(){
amount+=1;
console.log(amount); //實時打印amount的當(dāng)前值
return amount;
}
function minus(){
amount-=1;
console.log(amount); //實時打印amount的當(dāng)前值
return amount;
}
return add; //為該函數(shù)添加一個返回值,返回add函數(shù)
}
然后調(diào)用函數(shù)xxx:
var outer_add=xxx();
outer_add(); //結(jié)果為6
outer_add(); //結(jié)果為7
我們發(fā)現(xiàn)丙者,amount的值改變了趾痘,我們實現(xiàn)了我們想要實現(xiàn)的效果。
當(dāng)調(diào)用函數(shù)xxx時蔓钟,我們將xxx內(nèi)部的add函數(shù)作為操作amount的一個接口返回,outer_add函數(shù)和xxx內(nèi)部的add函數(shù)實際上是“一模一樣”的(因為它們都指向同一塊內(nèi)存)卵贱,而函數(shù)add可以訪問amount滥沫,因此我們可以通過outer_add來訪問amount。
需要注意的是键俱,由于內(nèi)部函數(shù)add在被訪問后一直處于被引用狀態(tài)兰绣,不能夠被垃圾回收。一旦我們不再使用outer_add函數(shù)编振,可以將其設(shè)置為null 缀辩,使其內(nèi)存得到釋放。
實際上,這就是閉包的典型用法⊥涡現(xiàn)在我們再回頭看看開頭提到的閉包的作用瓢阴,是否理解一些了呢?
細(xì)心的朋友可能已經(jīng)發(fā)現(xiàn)了健无,按這種做法荣恐,我們只能返回一個函數(shù)add,那函數(shù)minus怎么辦呢累贤?
我們這樣修改:
function xxx(){
var amount=5;
function add(){
amount+=1;
console.log(amount);
return amount;
}
function minus(){
amount-=1;
console.log(amount);
return amount;
}
return {
outer_add:add,
outer_minus:minus
}
}
然后調(diào)用函數(shù)xxx:
var outer=xxx();
outer.outer_add(); //結(jié)果為6
outer.outer_add(); //結(jié)果為7
outer.outer_minus(); //結(jié)果為6
這樣叠穆,我們雖然不能直接操作amount這個局部變量,但可以通過函數(shù)xxx暴露出的函數(shù)outer_add和outer_minus來間接操作amount臼膏。
我們也可以這樣修改:
function xxx(){
var amount=5;
function add(){
amount+=1;
console.log(amount);
return amount;
}
function minus(){
amount-=1;
console.log(amount);
return amount;
}
window.outer_add=add; //暴露outer_add函數(shù)硼被,可間接訪問amount
window.outer_minus=minus; //暴露outer_minus函數(shù),可間接訪問amount
}
然后調(diào)用函數(shù)xxx:
xxx();
outer_add(); //結(jié)果為6
outer_add(); //結(jié)果為7
outer_minus(); //結(jié)果為6
結(jié)果相同渗磅。
二嚷硫、立即執(zhí)行函數(shù)
1、什么是立即執(zhí)行函數(shù)
立即執(zhí)行函數(shù)夺溢,顧名思義论巍,聲明一個函數(shù),并立即執(zhí)行它风响。在上面的代碼中嘉汰,我們定義了一個函數(shù)xxx,并調(diào)用了它状勤。這個函數(shù)用于頁面初始化鞋怀,并且只會調(diào)用一次,因此我們可以使用立即執(zhí)行函數(shù)來代替函數(shù)xxx持搜。
改寫代碼如下:
function(){ //聲明了一個匿名函數(shù)
var amount=5;
function add(){
amount+=1;
console.log(amount);
return amount;
}
function minus(){
amount-=1;
console.log(amount);
return amount;
}
window.outer_add=add; //暴露outer_add函數(shù)密似,可間接訪問amount
window.outer_minus=minus; //暴露outer_minus函數(shù),可間接訪問amount
}(); //立即調(diào)用這個匿名函數(shù)
但是我們發(fā)現(xiàn)葫盼,瀏覽器竟然報錯了残腌。
首先我們區(qū)分一下函數(shù)聲明和函數(shù)表達式:區(qū)分函數(shù)聲明和函數(shù)表達式最簡單的方法是看function關(guān)鍵字出現(xiàn)在聲明中的位置。如果function是聲明中的第一個詞贫导,那么就是一個函數(shù)聲明抛猫,否則就是一個函數(shù)表達式。
所以孩灯,上述代碼之所以報錯闺金,是因為瀏覽器認(rèn)為函數(shù)聲明后再加 “(” 是錯誤的。所以我們需要通過加()峰档、+败匹、-寨昙、~、!等運算符掀亩,使上面的代碼變?yōu)楹瘮?shù)表達式舔哪,這樣就不會報錯了,如:
(function(){
var amount=5;
function add(){
amount+=1;
console.log(amount);
return amount;
}
function minus(){
amount-=1;
console.log(amount);
return amount;
}
window.outer_add=add; //暴露outer_add函數(shù)归榕,可間接訪問amount
window.outer_minus=minus; //暴露outer_minus函數(shù)尸红,可間接訪問amount
}());
然后我們就可以操作outer_add和outer_minus了:
outer_add(); //結(jié)果為6
outer_add(); //結(jié)果為7
outer_minus(); //結(jié)果為6
這就是立即執(zhí)行函數(shù)的作用,創(chuàng)建一個局部作用域刹泄,使不想暴露在全局的變量變?yōu)榫植孔兞俊?/p>
2外里、一個經(jīng)典面試題
題目如下:
for(var i=0;i<5;i++){
setTimeout(function(){
console.log(i);
},1000);
}
結(jié)果:1s后,連續(xù)打印出5個5特石。
但是我們的本意是1s后分別打印出0盅蝗、1、2姆蘸、3墩莫、4,這時逞敷,我們就可以利用閉包和立即執(zhí)行函數(shù):
for(var i=0;i<5;i++){
!function(j){
setTimeout(function(){
console.log(j);
},1000);
}(i);
}
結(jié)果:1s后狂秦,連續(xù)打印出0、1推捐、2裂问、3、4牛柒。
3堪簿、es6中的let
因為let聲明的變量是塊級作用域變量(而var聲明的變量為函數(shù)作用域變量),因此皮壁,我們可以利用let的特性來創(chuàng)建一個局部作用域椭更,從而取代立即執(zhí)行函數(shù):
{ //用塊級作用域{}代替立即執(zhí)行函數(shù)
let amount=5;
function add(){
amount+=1;
console.log(amount);
return amount;
}
function minus(){
amount-=1;
console.log(amount);
return amount;
}
window.outer_add=add; //暴露outer_add函數(shù),可間接訪問amount
window.outer_minus=minus; //暴露outer_minus函數(shù)蛾魄,可間接訪問amount
}
然后我們就可以操作outer_add和outer_minus了:
outer_add(); //結(jié)果為6
outer_add(); //結(jié)果為7
outer_minus(); //結(jié)果為6
塊級作用域的出現(xiàn)虑瀑,實際上使得獲得廣泛應(yīng)用的立即執(zhí)行函數(shù)表達式(IIFE)不再必要了。
這篇博客就先整理到這里滴须。由于個人水平有限缴川,博客錯誤之處,煩請指正描馅!