理解js中的閉包以及應(yīng)用場(chǎng)景

在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ò)誤或者不足之處,歡迎大家指正...

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容