深入理解閉包系列第一篇——到底什么才是閉包


前面的話

  閉包已經成為近乎神話的概念,它非常重要又難以掌握,而且還難以定義。本文就從閉包的定義說開去

 

古老定義

  閉包(closure),是指函數變量可以保存在函數作用域內,因此看起來是函數將變量“包裹”了起來

  那這樣說來,包含變量的函數就是閉包

//按照古老定義,包含變量n的函數foo就是閉包
function foo() {
    var n = 0;
}
console.log(n)//Uncaught ReferenceError: n is not defined

 

定義一

  閉包是指可以訪問其所在作用域的函數

  那這樣說來,需要通過作用域鏈查找變量的函數就是閉包

//按照定義一的說法,需要通過作用域鏈在全局環境中查找變量n的函數foo()就是閉包
var n = 0;
function foo() {
    console.log(n)//0
}
foo();

 

定義二

  閉包是指有權訪問另一個函數作用域中的變量的函數

  那這樣說來,訪問上層函數的作用域的內層函數就是閉包

//按照定義二的說法,嵌套在foo函數里的bar函數就是閉包
function foo(){
    var a = 2;
    function bar(){
        console.log(a); // 2
    }
    bar();
}
foo();

 

定義三

  閉包是指在函數聲明時的作用域以外的地方被調用的函數

  在函數聲明時的作用域以外的地方調用函數,需要通過將該函數作為返回值或者作為參數被傳遞

【1】返回值

//按照定義三的說法,在foo()函數的作用域中聲明,在全局環境的作用域中被調用的bar()函數是閉包
function foo(){
    var a = 2;
    function bar(){
        console.log(a); //2
    }
    return bar;
}
foo()();

  可以簡寫為如下表示:

function foo(){
    var a = 2;
    return function(){
        console.log(a);//2
    }
}
foo()();

【2】參數

//按照定義三的說法,在foo()函數的作用域中聲明,在bar()函數的作用域中被調用的baz()函數是閉包
function foo(){
    var a = 2;
    function baz(){
        console.log(a); //2
    }
    bar(baz);
}
function bar(fn){
    fn();
}

  因此,無論通過何種手段,只要將內部函數傳遞到所在的詞法作用域以外,它都會持有對原始作用域的引用,無論在何處執行這個函數都會使用閉包

 

IIFE

  IIFE是不是閉包呢?

  foo()函數在全局作用域定義,也在全局作用域被立即調用,如果按照定義一的說法來說,它是閉包。如果按照定義二和定義三的說法,它又不是閉包

var a = 2;
(function foo(){
    console.log(a);//2
})();

  還有一個更重要的原因是,在requireJS出現之前,實現模塊化編程主要通過IIFE,而在IIFE中常見的操作就是通過window.fn = fn來暴露接口,而這個fn就是閉包,而IIFE只是一個包含閉包的函數調用

(function(){
    var a = 0;
    function fn(){
        console.log(a); 
    }
    window.fn = fn;
})()
fn();

 

最后

  閉包定義之所以混亂,我覺得與經典書籍的不同解讀有關。經典定義是犀牛書的原話,定義二是高程的原話

  但,歸納起來就是關於一個函數要成為一個閉包到底需要滿意幾個條件

  嚴格來說,閉包需要滿足三個條件:【1】訪問所在作用域;【2】函數嵌套;【3】在所在作用域外被調用

  有些人覺得只滿足條件1就可以,所以IIFE是閉包;有些人覺得滿足條件1和2才可以,所以被嵌套的函數才是閉包;有些人覺得3個條件都滿足才可以,所以在作用域以外的地方被調用的函數才是閉包

  問題是,誰是權威呢?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM