什么是閉包?面試必看!

什么是閉包

什么是閉包,你可能會搜出很多答案....

《JavaScript高級程序設(shè)計》這樣描述:
閉包是指有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)。

《JavaScript權(quán)威指南》這樣描述:
從技術(shù)的角度講,所有的JavaScript函數(shù)都是閉包:它們都是對象,它們都關(guān)聯(lián)到作用域鏈。

《你不知道的JavaScript》這樣描述:
當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時,就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。

我最認(rèn)同的是《你不知道的JavaScript》中的描述,雖然前面的兩種說法都沒有錯,但閉包應(yīng)該是基于詞法作用域書寫代碼時產(chǎn)生的自然結(jié)果,是一種現(xiàn)象!你也不用為了利用閉包而特意的創(chuàng)建,因為閉包的在你的代碼中隨處可見,只是你還不知道當(dāng)時你寫的那一段代碼其實就產(chǎn)生了閉包。

講解閉包

上面已經(jīng)說到,當(dāng)函數(shù)可以記住并訪問所在的詞法作用域時,就產(chǎn)生了閉包,即使函數(shù)是在當(dāng)前詞法作用域之外執(zhí)行。

看一段代碼:

function fn1() {
    var name = 'iceman';
    function fn2() {
        console.log(name);
    }
    fn2();
}
fn1();

如果是根據(jù)《JavaScript高級程序設(shè)計》和《JavaScript權(quán)威指南》來說,上面的代碼已經(jīng)產(chǎn)生閉包了。fn2訪問到了fn1的變量,滿足了條件“有權(quán)訪問另一個函數(shù)作用域中的變量的函數(shù)”,fn2本身是個函數(shù),所以滿足了條件“所有的JavaScript函數(shù)都是閉包”。

這的確是閉包,但是這種方式定義的閉包不太好觀察。

再看一段代碼:

function fn1() {
    var name = 'iceman';
    function fn2() {
        console.log(name);
    }
    return fn2;
}
var fn3 = fn1();
fn3();

這樣就清晰地展示了閉包:

1.fn2的詞法作用域能訪問fn1的作用域
2.將fn2當(dāng)做一個值返回
3.fn1執(zhí)行后,將fn2的引用賦值給fn3
4.執(zhí)行fn3,輸出了變量name

我們知道通過引用的關(guān)系,fn3就是fn2函數(shù)本身。執(zhí)行fn3能正常輸出name,這不就是fn2能記住并訪問它所在的詞法作用域,而且fn2函數(shù)的運行還是在當(dāng)前詞法作用域之外了。

正常來說,當(dāng)fn1函數(shù)執(zhí)行完畢之后,其作用域是會被銷毀的,然后垃圾回收器會釋放那段內(nèi)存空間。而閉包卻很神奇的將fn1的作用域存活了下來,fn2依然持有該作用域的引用,這個引用就是閉包。

總結(jié):某個函數(shù)在定義時的詞法作用域之外的地方被調(diào)用,閉包可以使該函數(shù)極限訪問定義時的詞法作用域。

注意:對函數(shù)值的傳遞可以通過其他的方式,并不一定值有返回該函數(shù)這一條路,比如可以用回調(diào)函數(shù):

function fn1() {
    var name = 'iceman';
    function fn2() {
        console.log(name);
    }
    fn3(fn2);
}
function fn3(fn) {
    fn();
}
fn1();

本例中,將內(nèi)部函數(shù)fn2傳遞給fn3,當(dāng)它在fn3中被運行時,它是可以訪問到name變量的。

所以無論通過哪種方式將內(nèi)部的函數(shù)傳遞到所在的詞法作用域以外,它都回持有對原始作用域的引用,無論在何處執(zhí)行這個函數(shù)都會使用閉包。

再次解釋閉包

以上的例子會讓人覺得有點學(xué)院派了,但是閉包絕不僅僅是一個無用的概念,你寫過的代碼當(dāng)中肯定有閉包的身影,比如類似如下的代碼:

function waitSomeTime(msg, time) {
    setTimeout(function () {
        console.log(msg)
    }, time);
}
waitSomeTime('hello', 1000);

定時器中有一個匿名函數(shù),該匿名函數(shù)就有涵蓋waitSomeTime函數(shù)作用域的閉包,因此當(dāng)1秒之后,該匿名函數(shù)能輸出msg。

另一個很經(jīng)典的例子就是for循環(huán)中使用定時器延遲打印的問題:

for (var i = 1; i <= 10; i++) {
    setTimeout(function () {
        console.log(i);
    }, 1000);
}

在這段代碼中,我們對其的預(yù)期是輸出1~10,但卻輸出10次11。這是因為setTimeout中的匿名函數(shù)執(zhí)行的時候,for循環(huán)都已經(jīng)結(jié)束了,for循環(huán)結(jié)束的條件是i大于10,所以當(dāng)然是輸出10次11咯。

究其原因:i是聲明在全局作用中的,定時器中的匿名函數(shù)也是執(zhí)行在全局作用域中,那當(dāng)然是每次都輸出11了。

原因知道了,解決起來就簡單了,我們可以讓i在每次迭代的時候,都產(chǎn)生一個私有的作用域,在這個私有的作用域中保存當(dāng)前i的值。

for (var i = 1; i <= 10; i++) {
    (function () {
        var j = i;
        setTimeout(function () {
            console.log(j);
        }, 1000);
    })();
}

這樣就達(dá)到我們的預(yù)期了呀,讓我們用一種比較優(yōu)雅的寫法改造一些,將每次迭代的i作為實參傳遞給自執(zhí)行函數(shù),自執(zhí)行函數(shù)中用變量去接收:

for (var i = 1; i <= 10; i++) {
    (function (j) {
        setTimeout(function () {
            console.log(j);
        }, 1000);
    })(i);
}
閉包的應(yīng)用

閉包的應(yīng)用比較典型是定義模塊,我們將操作函數(shù)暴露給外部,而細(xì)節(jié)隱藏在模塊內(nèi)部:

function module() {
    var arr = [];
    function add(val) {
        if (typeof val == 'number') {
            arr.push(val);
        }
    }
    function get(index) {
        if (index < arr.length) {
            return arr[index]
        } else {
        return null;
        }
    }
    return {
        add: add,
        get: get
    }
}
var mod1 = module();
mod1.add(1);
mod1.add(2);
mod1.add('xxx');
console.log(mod1.get(2));
關(guān)于閉包還有很多要講,這里先講解比較基礎(chǔ)的概念,之后在做詳細(xì)講解。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

  • 一、作用域 A、定義 代碼在運行時,各個變量、函數(shù)和對象的可訪問性。換句話說,作用域決定了你的代碼里的變量和其他資...
    5吖閱讀 752評論 0 1
  • 目錄 概述 作用域編譯過程詞法作用域全局作用域函數(shù)作用域 閉包循環(huán)和閉包閉包的用途性能 總結(jié) 概述 作用域和閉包一...
    許驍Charles閱讀 565評論 0 1
  • [圖片上傳失敗...(image-9ad041-1561218142988)] 目錄 概述 作用域編譯過程詞法作用...
    M1mmm閱讀 302評論 0 1
  • 提升(var) 我們都認(rèn)為JavaScript代碼執(zhí)行的時候是由上到下一行一行執(zhí)行的。但實際上這并不完全正確,有一...
    hhooke閱讀 1,940評論 0 1
  • 使用爬蟲的時候記得進(jìn)入虛擬環(huán)境 查看已有的虛擬環(huán)境 workon 創(chuàng)建虛擬環(huán)境(創(chuàng)建完之后自動進(jìn)入運行虛擬環(huán)境) ...
    浮_屠閱讀 598評論 0 0

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