One Day One Tip 之 閉包
時間:2016-08-30 13:39:33
作者:zhongxia
總結(jié):
概念:
閉包:能夠讀取其他函數(shù)內(nèi)部變量的函數(shù),在JavaScript中,一個函數(shù)return它內(nèi)部的一個函數(shù)。
原理:通過引用變量從而阻止該變量被垃圾回收的機(jī)制
優(yōu):
- 封裝私有屬性和私有方法,加強(qiáng)封裝性,可以達(dá)到對變量的保護(hù)作用。
- 更好的組織代碼,比如模塊化
缺:
- 增加了內(nèi)存的消耗,并且在某些瀏覽器下,由于垃圾回收機(jī)制不同,有可能導(dǎo)致內(nèi)存溢出
- 增加復(fù)雜度
- 由于閉包內(nèi)部變量優(yōu)先級高于外部變量,所以多查找作用域鏈中的一個層次,就會在一定程度上影響查找速度。
零、所需知識
要理解閉包,首先必須理解 JavaScript 特殊的變量作用域。
變量的作用域無非就是兩種: 全局變量 和 局部變量
JavaScript語言的特殊之處,就在于 函數(shù)內(nèi)部可以直接讀取全局變量。
不使用 var 聲明的變量,則為全局變量。 b = 100;
function fn(){
var a = b = 1;
// ==> var a = window.b = 1; // a 是局部變量 b 是全局變量
}
fn();
console.log("b = ",b); // 1
console.log("a = ",a); //VM783:1 Uncaught ReferenceError: a is not defined
函數(shù)外部無法訪問局部變量。 因此在外部,訪問 a 變量 報錯。 而 b 變量是 全局變量,因此可以訪問到。
一、閉包是什么
閉包是概念?
閉包是指某種程序語言中的代碼塊允許一級函數(shù)存在并且在一級函數(shù)中所定義的自由變量能不被釋放,直到一級函數(shù)被釋放前,一級函數(shù)外也能應(yīng)用這些未釋放的自由變量。
我的理解就是:閉包就是能夠讀取其他函數(shù)內(nèi)部變量的函數(shù)
由于在JavaScript語言中,只有函數(shù)內(nèi)部的子函數(shù)才能讀取局部變量,因此可以把閉包簡單的理解成 『定義在一個函數(shù)內(nèi)部的函數(shù)』
本質(zhì)上: 閉包就是將函數(shù)內(nèi)部和外部鏈接起來的一座橋梁。
//eg: example
function a(x){
var tmp = 10;
return function(y){
return (x+y)+(++tmp);
}
}
var b = a(10);
b(5); //26, 每執(zhí)行一次 tmp 加 1
因為 a() 執(zhí)行后,返回 的 方法 b, 內(nèi)部引用了 tmp 變量, 導(dǎo)致 tmp 變量的標(biāo)記+1, 垃圾回收機(jī)制就不會清除 tmp這個變量。
然后外部就可以繼續(xù)訪問到 tmp 變量。
二、閉包的作用
- 讀取函數(shù)內(nèi)部的變量
- 讓內(nèi)部的變量始終保持在內(nèi)存中
- 設(shè)計私有方法和變量【封裝框架的時候更明顯,典型的如Jquery】
var jQuery = (function(){
var jQuery = function(){
//TODO
}
return (window.$ = window.jQuery = jQuery);
});
三、閉包的優(yōu)缺點
優(yōu)點
- 延長作用域鏈。
- 更好的組織代碼,比如模塊化,異步代碼轉(zhuǎn)同步等。
- 加強(qiáng)封裝性,可以打到對變量的保護(hù)(第二點的加強(qiáng))
- 處理異步造成的變量不能即時傳遞的問題
缺點
- 由于閉包會使得函數(shù)中的變量都被保存在內(nèi)存中,內(nèi)存消耗很大,所以不能濫用閉包,否則會造成網(wǎng)頁的性能問題,在IE中可能導(dǎo)致內(nèi)存泄露。解決方法是,在退出函數(shù)之前,將不使用的局部變量全部刪除
- 增加內(nèi)存的消耗
- IE瀏覽器上 因為回收機(jī)制,有內(nèi)存溢出的風(fēng)險
- 增加了代碼復(fù)雜度
四、閉包用法實戰(zhàn)
1. 對fun的計算方式進(jìn)行定制
實際使用的時候,閉包可以創(chuàng)建出非常優(yōu)雅的設(shè)計,允許對funarg上定義的多種計算方式進(jìn)行定制。如下就是數(shù)組排序的例子,它接受一個排序條件函數(shù)作為參數(shù):
[1, 2, 3].sort(function (a, b) {
... // 排序條件
});
同樣的例子還有,數(shù)組的map方法是根據(jù)函數(shù)中定義的條件將原數(shù)組映射到一個新的數(shù)組中:
[1, 2, 3].map(function (element) {
return element * 2;
}); // [2, 4, 6]
2. 函數(shù)式參數(shù)
使用函數(shù)式參數(shù),可以很方便的實現(xiàn)一個搜索方法,并且可以支持無限制的搜索條件:
someCollection.find(function (element) {
return element.someProperty == 'searchCondition';
});
還有應(yīng)用函數(shù),比如常見的forEach方法,將函數(shù)應(yīng)用到每個數(shù)組元素:
[1, 2, 3].forEach(function (element) {
if (element % 2 != 0) {
alert(element);
}
}); // 1, 3
順便提下,函數(shù)對象的 apply 和 call方法,在函數(shù)式編程中也可以用作應(yīng)用函數(shù)。 apply和call已經(jīng)在討論“this”的時候介紹過了;這里,我們將它們看作是應(yīng)用函數(shù) —— 應(yīng)用到參數(shù)中的函數(shù)(在apply中是參數(shù)列表,在call中是獨立的參數(shù)):
(function () {
alert([].join.call(arguments, ';')); // 1;2;3
}).apply(this, [1, 2, 3]);
3. 延遲調(diào)用
閉包還有另外一個非常重要的應(yīng)用 —— 延遲調(diào)用:
var a = 10;
setTimeout(function () {
alert(a); // 10, after one second
}, 1000);
4. 回調(diào)函數(shù)
//...
var x = 10;
// only for example
xmlHttpRequestObject.onreadystatechange = function () {
// 當(dāng)數(shù)據(jù)就緒的時候,才會調(diào)用;
// 這里,不論是在哪個上下文中創(chuàng)建
// 此時變量“x”的值已經(jīng)存在了
alert(x); // 10
};
//...
5. 創(chuàng)建封裝的作用域來隱藏輔助對象:
var foo = {};
// 初始化
(function (object) {
var x = 10;
object.getX = function _getX() {
return x;
};
})(foo);
alert(foo.getX()); // 獲得閉包 "x" – 10