閉包的總結(乾貨1)

閉包:函數能夠記住所在詞法做用域,就產生了閉包,即便函數在當前詞法做用域以外執行 ,閉包無處不在。。。請忽略這句話 ,看完在說javascript

function foo() {
        var a = 1;
        function bibao() {
            console.log(a);
        }
        return bibao;
    }
    var baz = foo();
    baz();

bibao()能訪問foo的內部做用域,而後bibao()自己做爲值傳遞給baz,在foo執行以後,注意 baz=foo(),這裏foo()會先運行,而後將返回值賦值給baz,而後運行baz(),實際是經過不一樣的標識符引用內部的函數bibao();bibao()能夠正常的執行,實現了bibao()在本身定義時的詞法做用域之外執行。foo執行完成以後,一般期待foo()整個內部空間被銷燬,被垃圾回收器回收空間,可是,拜baz()所賜,baz能訪問foo()做用域,使得該做用域一直純在。java

這個例子中,bibao對做用域的引用就叫閉包閉包

 

再來看一個傳遞函數是間接的:函數

var fn;
    function foo() {
        var a=2;
        function bibao() {
            console.log(a)
        }
        fn=bibao;
    }
    function bar() {
        fn(); 
    }
    foo();
    bar();

同上面例子,不過這裏外部函數運行,實現了全局變量fn對bibao()的引用,bibao()能訪問foo內部詞法做用域,在運行fn()的時候 ,就能實現了閉包spa

再好比:code

function foo() {
var a = 2;
function baz() {
console.log( a ); // 2
}
bar( baz );
}
function bar(fn) {
fn(); // 閉包
}

運行foo()實現baz的引用綁定到bar做用域 ,bar是全局函數對象

總結:blog

不管經過何種手段將內部函數傳遞到所在的詞法做用域之外,它都會持有對原始定義做用
域的引用,不管在何處執行這個函數都會使用閉包。
接口

在好比咱們常常寫的函數:事件

function wait(message) {
        setTimeout( function timer() {
            console.log( message );
        }, 1000 );
    }
    wait( "Hello, closure!" );

這也是閉包

將一個內部函數(名爲timer)傳遞給setTimeout(..)。timer 具備涵蓋wait(..) 做用域的閉包,所以還保有對變量message 的引用。
wait(..) 執行1000 毫秒後,它的內部做用域並不會消失,timer 函數依然保有wait(..)做用域的閉包(也就是引用)。

function setupBot(name, selector) {
$( selector ).click( function activator() {
console.log( "Activating: " + name );
} );
}
setupBot( "Closure Bot 1", "#bot_1" );
setupBot( "Closure Bot 2", "#bot_2" );

同理:事件函數也是閉包,本質上只要用到回調函數的都是使用閉包


在好比:

for (var i=1; i<=5; i++) {
        setTimeout( function timer() {
            console.log( i );   //結果是輸出5次6
        }, i*1000 );
     }   

顯而易見的,延遲函數的回調會在循環結束時才執行,爲何會捕捉不到?

它們被封閉在一個共享的全局做用域中,所以實際上只有一個i

解決辦法:延遲函數的回調重複定義五次,徹底不使用循環,用匿名函數當即執行產生5個數做用域包裹對應變量i

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

固然,你也能夠直接用ES新特性:

for (let i=1; i<=5; i++) {
        setTimeout( function timer() {
            console.log( i );
        }, i*1000 );
    }

延遲函數的回調能夠將新的做用域封閉在每一個迭代內部,每一個迭代中都會含有一個具備正確值的變量供咱們訪問

在好比:模塊的例子,一個函數返回一個公用的對象接口

function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
}
var foo = CoolModule();
foo.doSomething(); // cool
foo.doAnother(); // 1 ! 2 ! 3

同理,調用CoolModule();實現了內部函數引用傳遞給外部詞法做用域 ,foo就能訪問CoolModule內部

1. 必須有外部的封閉函數,該函數必須至少被調用一次(每次調用都會建立一個新的模塊
實例)。
2. 封閉函數必須返回至少一個內部函數,這樣內部函數才能在私有做用域中造成閉包,並
且能夠訪問或者修改私有的狀態。

實現單例模式

var foo = (function CoolModule() {
var something = "cool";
var another = [1, 2, 3];
function doSomething() {
console.log( something );
}
function doAnother() {
console.log( another.join( " ! " ) );
}
return {
doSomething: doSomething,
doAnother: doAnother
};
})();

 

大量代碼例子出自,原諒我剽竊,實在是讓人豁然開朗

《Javascript 權威指南》

《你不知道的javascript》

相關文章
相關標籤/搜索