前面的話
根據閉包的定義,我們知道,無論通過何種手段,只要將內部函數傳遞到所在的詞法作用域以外,它都會持有對原始作用域的引用,無論在何處執行這個函數都會使用閉包。接下來,本文將詳細介紹閉包的10種形式
返回值
最常用的一種形式是函數作為返回值被返回
var F = function(){ var b = 'local'; var N = function(){ return b; } return N; } console.log(F()());
函數賦值
一種變形的形式是將內部函數賦值給一個外部變量
var inner; var F = function(){ var b = 'local'; var N = function(){ return b; }; inner = N; }; F(); console.log(inner());
函數參數
閉包可以通過函數參數傳遞函數的形式來實現
var Inner = function(fn){ console.log(fn()); } var F = function(){ var b = 'local'; var N = function(){ return b; } Inner(N); } F();
IIFE
由前面的示例代碼可知,函數F()都是在聲明后立即被調用,因此可以使用IIFE來替代。但是,要注意的是,這里的Inner()只能使用函數聲明語句的形式,而不能使用函數表達式。詳細原因移步至此
function Inner(fn){ console.log(fn()); } (function(){ var b = 'local'; var N = function(){ return b; } Inner(N); })();
循環賦值
在閉包問題上,最常見的一個錯誤就是循環賦值的錯誤。關於其錯誤原因的詳細解釋移步至此
function foo(){ var arr = []; for(var i = 0; i < 2; i++){ arr[i] = function(){ return i; } } return arr; } var bar = foo(); console.log(bar[0]());//2
正確的寫法如下
function foo(){ var arr = []; for(var i = 0; i < 2; i++){ arr[i] = (function fn(j){ return function test(){ return j; } })(i); } return arr; } var bar = foo(); console.log(bar[0]());//0
g(s)etter
我們通過提供getter()和setter()函數來將要操作的變量保存在函數內部,防止其暴露在外部
var getValue,setValue; (function(){ var secret = 0; getValue = function(){ return secret; } setValue = function(v){ if(typeof v === 'number'){ secret = v; } } })(); console.log(getValue());//0 setValue(1); console.log(getValue());//1
迭代器
我們經常使用閉包來實現一個累加器
var add = (function(){ var counter = 0; return function(){ return ++counter; } })(); console.log(add())//1 console.log(add())//2
類似地,使用閉包可以很方便的實現一個迭代器
function setup(x){ var i = 0; return function(){ return x[i++]; } } var next = setup(['a','b','c']); console.log(next());//'a' console.log(next());//'b' console.log(next());//'c'
區分首次
var firstLoad = (function(){ var _list = []; return function(id){ if(_list.indexOf(id) >= 0){ return false; }else{ _list.push(id); return true; } } })(); firstLoad(10);//true firstLoad(10);//false firstLoad(20);//true firstLoad(20);//false
緩存機制
通過閉包加入緩存機制,使得相同的參數不用重復計算,來提高函數的性能
未加入緩存機制前的代碼如下
var mult = function(){ var a = 1; for(var i = 0,len = arguments.length; i<len; i++){ a = a * arguments[i]; } return a; }
加入緩存機制后,代碼如下
var mult = function(){ var cache = {}; var calculate = function(){ var a = 1; for(var i = 0,len = arguments.length; i<len; i++){ a = a * arguments[i]; } return a; }; return function(){ var args = Array.prototype.join.call(arguments,','); if(args in cache){ return cache[args]; }
return cache[args] = calculate.apply(null,arguments); } }()
img對象
img對象經常用於數據上報
var report = function(src){ var img = new Image(); img.src = src; } report('http://xx.com/getUserInfo');
但是,在一些低版本瀏覽器中,使用report函數進行數據上報會丟失30%左右的數據,也就是說,report函數並不是每一次都成功地發起了HTTP請求
原因是img是report函數中的局部變量,當report函數的調用結束后,img局部變量隨即被銷毀,而此時或許還沒來得及發出HTTP請求,所以此次請求就會丟失掉
現在把img變量用閉包封閉起來,就能解決請求丟失的問題
var report = (function(){ var imgs = []; return function(src){ var img = new Image(); imgs.push(img); img.src = src; } })() report('http://xx.com/getUserInfo');