在js中的學(xué)習(xí)中,總會(huì)遇到一個(gè)陌生又晦澀,然后還是陌生的詞匯,那就是閉包。
首先,什么是閉包?
其次,閉包的作用是什么呢?
最后,什么時(shí)候用得到閉包呢?
接下來(lái),我們就來(lái)回答這三個(gè)問(wèn)題。
1.什么是閉包?
首先,閉包的概念:
閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)??梢岳斫獬伞岸x在一個(gè)函數(shù)內(nèi)部的函數(shù)“。在本質(zhì)上,閉包是將函數(shù)內(nèi)部和函數(shù)外部連接起來(lái)的橋梁。
看完這段話,是不是覺得???
那么,來(lái)看一個(gè)例子:
想要實(shí)現(xiàn)一個(gè)函數(shù),每次調(diào)用的時(shí)候,函數(shù)內(nèi)的變量實(shí)現(xiàn)加1。
function fn(){
var a = 10;
a++;
console.log(a);
}
var f = fn();
f();
f();
f();
f();
這樣,每次調(diào)用fn,得到的a都是11 。這是因?yàn)槊看握{(diào)用的時(shí)候,都會(huì)重新聲明a。若是不想每次獲取到都是同一個(gè)值,每次就不能重新聲明變量,這樣,我們就可以將聲明放在函數(shù)的外面,即:
var a = 10;
function fn(){
a++;
console.log(a);
}
var f = fn();
f();
f();
f();
f();
但是,這樣的話,a就變成了全局變量,會(huì)造成全局變量命名空間的污染。所以,這個(gè)時(shí)候,我們需要閉包。
function addOne(){
var a = 10;
// a是局部變量,使用閉包后,a進(jìn)化成addOne或者fn的私有(自由)變量,在函數(shù)外面可以操作a的值,每次進(jìn)行加1
function fn(){
a++;
console.log(a);
}
return fn;
}
var f = addOne(); // addOne只執(zhí)行一次,var a = 10只執(zhí)行一次,只定義了一次
f(); // 11 每次執(zhí)行fn,a都會(huì)加1
f(); // 12
f(); // 13
f(); // 14
現(xiàn)在是不是覺得對(duì)閉包有點(diǎn)感覺了呢?
在闡述閉包作用之前,值得一提的是閉包的原理。那么,我們來(lái)說(shuō)一下閉包的原理:
1.計(jì)算機(jī)原理:垃圾回收機(jī)制
計(jì)算機(jī)的垃圾回收機(jī)制:將要?jiǎng)h除的信息暫存,下次可以再次使用,而不是立即刪除
在閉包中的體現(xiàn):利用作用域的嵌套,臨時(shí)保存即將刪除的變量,觸發(fā)計(jì)算機(jī)的垃圾回收機(jī)制
2.函數(shù)的執(zhí)行原理:
兩個(gè)概念:定義作用域,執(zhí)行作用域
顧名思義,定義作用域就是函數(shù)定義的作用域,執(zhí)行作用域就是函數(shù)在執(zhí)行時(shí)所在的作用域。
在js中規(guī)定:函數(shù)在執(zhí)行時(shí),可以讀取自身定義作用域中的變量
2.閉包的作用是?
(1)局部變量可以在全局空間內(nèi)操作
(2)將可能被刪除或覆蓋的局部變量,臨時(shí)保存,不被刪除或覆蓋
解釋完閉包的概念、原理、作用,接下來(lái)就是我們最關(guān)心的,什么時(shí)候使用閉包呢?
3.閉包的應(yīng)用場(chǎng)景?
場(chǎng)景1:for循環(huán)內(nèi)部函數(shù)需要獲取計(jì)數(shù)器
問(wèn)題闡述:for循環(huán)之中的i變量會(huì)因?yàn)閒or的循環(huán)次數(shù)被覆蓋,所以在for循環(huán)內(nèi)部存在函數(shù)時(shí),而且這個(gè)函數(shù)內(nèi)會(huì)調(diào)用變量i,這種情況下就需要用到閉包。
<body>
<ul>
<li>link1</li>
<li>link2</li>
<li>link3</li>
<li>link4</li>
<li>link5</li>
</ul>
</body>
<script type="text/javascript">
var ali = document.querySelectorAll("li");
for(var i=0;i<ali.length;i++){
(function(i){
ali[i].onclick = function(){
console.log(i);
}
})(i);
}
</script>
解釋:for循環(huán)執(zhí)行很快,將每次的i的保存到匿名函數(shù)的實(shí)參中,通過(guò)匿名函數(shù)自動(dòng)執(zhí)行傳給形參,在匿名函數(shù)中可以獲取到i,點(diǎn)擊事件函數(shù)的定義作用域也是執(zhí)行作用域,所以在事件執(zhí)行函數(shù)中可以獲取外層匿名函數(shù)的形參i,這樣每次就可以在事件函數(shù)中獲取到for循環(huán)的計(jì)數(shù)器i。
友情提示:這里let也可以實(shí)現(xiàn),利用了let的塊級(jí)作用域可以形成閉包的原理,且let方式更簡(jiǎn)單。
<body>
<ul>
<li>link1</li>
<li>link2</li>
<li>link3</li>
<li>link4</li>
<li>link5</li>
</ul>
</body>
<script type="text/javascript">
var ali = document.querySelectorAll("li");
for(let i=0;i<ali.length;i++){
ali[i].onclick = function(){
console.log(i);
}
}
</script>
場(chǎng)景2:異步的回調(diào)函數(shù)中要讀取外部實(shí)時(shí)被修改的變量
smallFire(){
for(var i=0;i<num;i++){
let div = document.createElement("div");
move(div,{
left:t.x,
top:t.y
},function(){
// 因?yàn)槁暶餍熁〞r(shí)使用的let,會(huì)觸發(fā)塊級(jí)作用域,每次循環(huán)執(zhí)行時(shí),move及自身的回調(diào)函數(shù)會(huì)隨之開啟,都處在對(duì)應(yīng)的塊級(jí)作用域內(nèi),所以能夠拿到每個(gè)塊級(jí)作用域的div,可以刪除
div.remove();
})
}
}
解釋:因?yàn)檠h(huán)會(huì)立即執(zhí)行,每次循環(huán)創(chuàng)建的小煙花,需要單獨(dú)保存(或單獨(dú)使用(運(yùn)動(dòng)和刪除)),所以為了防止循環(huán)創(chuàng)建的元素被覆蓋,使用let聲明變量,觸發(fā)塊級(jí)作用域,保存變量,方便將來(lái)操作。
下面是一個(gè)封裝好的緩沖運(yùn)動(dòng)的函數(shù):
function move(ele,data,cb){
clearInterval(ele.t);
ele.t = setInterval(function(){
var onoff = true;
for(var i in data){
var iNow = parseInt(getStyle(ele,i));
var speed = (data[i] - iNow)/10;
speed = speed<0 ? Math.floor(speed) : Math.ceil(speed);
if(iNow != data[i]){
onoff = false;
}
ele.style[i] = iNow + speed + "px";
}
if(onoff){
clearInterval(ele.t);
cb && cb();
}
},30)
}
場(chǎng)景3:可以給無(wú)法處理函數(shù)參數(shù)的函數(shù),處理參數(shù)
需求:每隔3秒鐘,打印一個(gè)hello
問(wèn)題1:這里say函數(shù)加了括號(hào),就立即執(zhí)行了,不再等3秒了。執(zhí)行之后,函數(shù)的返回值是undefined,結(jié)果setTimeout的第一個(gè)參數(shù)就是undefined。
setTimeout(say("hello"),3000);
function say(a){
console.log(a);
}
問(wèn)題2:所以say不能加括號(hào),那么say就沒(méi)法傳參了
setTimeout(say,3000);
function say(a){
console.log(a);
}
解決:所以,say必須得加括號(hào)且傳參。這樣,延遲器的第一個(gè)必須是一個(gè)函數(shù),所以在say函數(shù)中,返回一個(gè)函數(shù)即可,在函數(shù)中執(zhí)行想要的操作。
setTimeout(say("你好"),3000);
function say(a){
return function(){
console.log(a);
}
}
這里,就利用到了函數(shù)的執(zhí)行作用域可以使用函數(shù)的定義作用域里面的變量這個(gè)原理。因?yàn)閮?nèi)層函數(shù)的定義作用域是say(a){這里即是內(nèi)層函數(shù)的定義作用域,可以拿到這里定義的所有的變量,包括say函數(shù)的形參a}。
看完應(yīng)用場(chǎng)景之后,是不是覺得閉包還真有點(diǎn)意思。然而,世上沒(méi)有十全十美的事物,閉包也存在其缺點(diǎn):
內(nèi)存消耗很大,不能濫用;閉包會(huì)在父函數(shù)外部,改變父函數(shù)內(nèi)部變量的值。
是不是覺得意猶未盡?想挖掘更多關(guān)于閉包的特點(diǎn)呢?期待大家的分享
今天的分享到此結(jié)束,如果文中有什么錯(cuò)誤或者不足之處,歡迎大家指正...