深刻理解閉包系列第一篇——到底什麼纔是閉包

前面的話

  閉包已經成爲近乎神話的概念,它很是重要又難以掌握,並且還難以定義。本文就從閉包的定義說開去編程

 

古老定義

  閉包(closure),是指函數變量能夠保存在函數做用域內,所以看起來是函數將變量「包裹」了起來閉包

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

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

 

定義一

  閉包是指能夠訪問其所在做用域的函數函數

  那這樣說來,須要經過做用域鏈查找變量的函數就是閉包ui

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

 

定義二

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

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

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

 

定義三

  閉包是指在函數聲明時的做用域之外的地方被調用的函數blog

  在函數聲明時的做用域之外的地方調用函數,須要經過將該函數做爲返回值或者做爲參數被傳遞接口

【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個條件都知足才能夠,因此在做用域之外的地方被調用的函數纔是閉包

  問題是,誰是權威呢?

相關文章
相關標籤/搜索