在js中的學習中,總會遇到一個陌生又晦澀椿访,然后還是陌生的詞匯乌企,那就是閉包。
首先成玫,什么是閉包加酵?
其次,閉包的作用是什么呢哭当?
最后猪腕,什么時候用得到閉包呢?
接下來钦勘,我們就來回答這三個問題陋葡。
1.什么是閉包?
首先个盆,閉包的概念:
閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)脖岛《淦埽可以理解成“定義在一個函數(shù)內(nèi)部的函數(shù)“颊亮。在本質(zhì)上,閉包是將函數(shù)內(nèi)部和函數(shù)外部連接起來的橋梁陨溅。
看完這段話终惑,是不是覺得?门扇?雹有?
那么偿渡,來看一個例子:
想要實現(xiàn)一個函數(shù),每次調(diào)用的時候霸奕,函數(shù)內(nèi)的變量實現(xiàn)加1溜宽。
function fn(){
var a = 10;
a++;
console.log(a);
}
var f = fn();
f();
f();
f();
f();
這樣,每次調(diào)用fn质帅,得到的a都是11 适揉。這是因為每次調(diào)用的時候,都會重新聲明a煤惩。若是不想每次獲取到都是同一個值嫉嘀,每次就不能重新聲明變量,這樣魄揉,我們就可以將聲明放在函數(shù)的外面誓沸,即:
var a = 10;
function fn(){
a++;
console.log(a);
}
var f = fn();
f();
f();
f();
f();
但是峰鄙,這樣的話,a就變成了全局變量,會造成全局變量命名空間的污染痊焊。所以,這個時候欣除,我們需要閉包兔朦。
function addOne(){
var a = 10;
// a是局部變量,使用閉包后摇零,a進化成addOne或者fn的私有(自由)變量推掸,在函數(shù)外面可以操作a的值,每次進行加1
function fn(){
a++;
console.log(a);
}
return fn;
}
var f = addOne(); // addOne只執(zhí)行一次驻仅,var a = 10只執(zhí)行一次谅畅,只定義了一次
f(); // 11 每次執(zhí)行fn,a都會加1
f(); // 12
f(); // 13
f(); // 14
現(xiàn)在是不是覺得對閉包有點感覺了呢噪服?
在闡述閉包作用之前毡泻,值得一提的是閉包的原理。那么粘优,我們來說一下閉包的原理:
1.計算機原理:垃圾回收機制
計算機的垃圾回收機制:將要刪除的信息暫存仇味,下次可以再次使用,而不是立即刪除
在閉包中的體現(xiàn):利用作用域的嵌套,臨時保存即將刪除的變量雹顺,觸發(fā)計算機的垃圾回收機制
2.函數(shù)的執(zhí)行原理:
兩個概念:定義作用域丹墨,執(zhí)行作用域
顧名思義,定義作用域就是函數(shù)定義的作用域嬉愧,執(zhí)行作用域就是函數(shù)在執(zhí)行時所在的作用域贩挣。
在js中規(guī)定:函數(shù)在執(zhí)行時,可以讀取自身定義作用域中的變量
2.閉包的作用是?
(1)局部變量可以在全局空間內(nèi)操作
(2)將可能被刪除或覆蓋的局部變量,臨時保存王财,不被刪除或覆蓋
解釋完閉包的概念卵迂、原理、作用绒净,接下來就是我們最關(guān)心的见咒,什么時候使用閉包呢?
3.閉包的應(yīng)用場景挂疆?
場景1:for循環(huán)內(nèi)部函數(shù)需要獲取計數(shù)器
問題闡述:for循環(huán)之中的i變量會因為for的循環(huán)次數(shù)被覆蓋论颅,所以在for循環(huán)內(nèi)部存在函數(shù)時,而且這個函數(shù)內(nèi)會調(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ù)自動執(zhí)行傳給形參今妄,在匿名函數(shù)中可以獲取到i,點擊事件函數(shù)的定義作用域也是執(zhí)行作用域鸳碧,所以在事件執(zhí)行函數(shù)中可以獲取外層匿名函數(shù)的形參i,這樣每次就可以在事件函數(shù)中獲取到for循環(huán)的計數(shù)器i盾鳞。
友情提示:這里let也可以實現(xiàn),利用了let的塊級作用域可以形成閉包的原理瞻离,且let方式更簡單腾仅。
<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>
場景2:異步的回調(diào)函數(shù)中要讀取外部實時被修改的變量
smallFire(){
for(var i=0;i<num;i++){
let div = document.createElement("div");
move(div,{
left:t.x,
top:t.y
},function(){
// 因為聲明小煙花時使用的let,會觸發(fā)塊級作用域,每次循環(huán)執(zhí)行時,move及自身的回調(diào)函數(shù)會隨之開啟,都處在對應(yīng)的塊級作用域內(nèi),所以能夠拿到每個塊級作用域的div,可以刪除
div.remove();
})
}
}
解釋:因為循環(huán)會立即執(zhí)行,每次循環(huán)創(chuàng)建的小煙花,需要單獨保存(或單獨使用(運動和刪除)),所以為了防止循環(huán)創(chuàng)建的元素被覆蓋,使用let聲明變量,觸發(fā)塊級作用域,保存變量,方便將來操作套利。
下面是一個封裝好的緩沖運動的函數(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)
}
場景3:可以給無法處理函數(shù)參數(shù)的函數(shù),處理參數(shù)
需求:每隔3秒鐘推励,打印一個hello
問題1:這里say函數(shù)加了括號,就立即執(zhí)行了肉迫,不再等3秒了验辞。執(zhí)行之后,函數(shù)的返回值是undefined喊衫,結(jié)果setTimeout的第一個參數(shù)就是undefined跌造。
setTimeout(say("hello"),3000);
function say(a){
console.log(a);
}
問題2:所以say不能加括號,那么say就沒法傳參了
setTimeout(say,3000);
function say(a){
console.log(a);
}
解決:所以族购,say必須得加括號且傳參壳贪。這樣,延遲器的第一個必須是一個函數(shù)寝杖,所以在say函數(shù)中违施,返回一個函數(shù)即可,在函數(shù)中執(zhí)行想要的操作朝墩。
setTimeout(say("你好"),3000);
function say(a){
return function(){
console.log(a);
}
}
這里醉拓,就利用到了函數(shù)的執(zhí)行作用域可以使用函數(shù)的定義作用域里面的變量這個原理伟姐。因為內(nèi)層函數(shù)的定義作用域是say(a){這里即是內(nèi)層函數(shù)的定義作用域收苏,可以拿到這里定義的所有的變量亿卤,包括say函數(shù)的形參a}。
看完應(yīng)用場景之后鹿霸,是不是覺得閉包還真有點意思排吴。然而,世上沒有十全十美的事物懦鼠,閉包也存在其缺點:
內(nèi)存消耗很大钻哩,不能濫用;閉包會在父函數(shù)外部肛冶,改變父函數(shù)內(nèi)部變量的值街氢。
是不是覺得意猶未盡?想挖掘更多關(guān)于閉包的特點呢睦袖?期待大家的分享
今天的分享到此結(jié)束珊肃,如果文中有什么錯誤或者不足之處,歡迎大家指正...