首先說明下...閉包是js高級特性之一...但並不是js獨有...perl, python, php(5.3以上版本) 都是支持閉包的..php
官方解釋: 所謂「閉包」,指的是一個擁有許多變量和綁定了這些變量的環境的表達式(一般是一個函數),於是這些變量也是該表達式的一部分前端
john resig解釋: 閉包就是內部函數能夠訪問外部函數中所定義的變量,即便該函數已經執行結束。
python
看..不愧是jquery大牛.. 一語中地..
若是你仍是不能明白上面那句話...那麼我就換句話來講:
在js中...執行一個函數A...當函數A執行完後...理論上來說...改函數A內全部被定義的 臨時變量都將被 當成可回收的垃圾等待垃圾回收....然而在這個過程..有一種臨時變量是沒法被垃圾回收的...當A函數中有一個內部函數a時.a函數內引用了A中定義的臨時變量...而且a函數在A函數執行完後..仍然能夠被外部訪問到時...被a函數所引用的臨時變量就沒法被當成垃圾等待垃圾回收.. 而a函數能夠被外部訪問的同時..就生成了一個閉包...jquery
舉個例子吧..也是比較經典的例子
//函數A 執行完後 它將返回一個函數a
function A(){
//定義一個臨時變量
var x = 1;
//返回一個內部函數a
//執行時打印臨時變量x
return function a(){
console.log( x );
};
}ajax
//執行A 獲得內部函數a
//此時內部函數a被返回...它引用了臨時變量x
//理論上A執行後 x作爲臨時變量將被當成垃圾等待垃圾回收
//可是因爲內部函數a引用了x 因此此時就生成了一個閉包
var a = A();緩存
//執行a 打印1
a(); //1 閉包
閉包並不是定義函數時就生成的...而是在執行過程當中 當a函數被當成一個返回值被返回時 纔會生成一個閉包..dom
閉包容易誤解的地方:異步
1。 閉包老是在匿名函數中生成的函數
閉包並不是都是在匿名函數中生成的..好比上一段代碼中...被返回的函數有命名-a
2。 閉包在定義時產生的...
閉包並不是是在定義時產生的...而是在內部函數可被外部訪問到時纔會產生...
3。 閉包很強大..用的越多就越牛A(==!)
不否定閉包很強大.....可是並不是用的越多就是越好的...使用閉包..會形成調試困難..因此要習慣作標識..另外...使用閉包會涉及到 增加函數做用域的 形成內部函數訪問全局變量變慢的問題...
PHP中的閉包
php-5.3 以上版本其中一個更新就是使php支持了簡單的閉包
<?php
/**
* 一個curry的加法函數
* @param unknown_type $start 起始值
* @return unknown 返回一個匿名函數
*/
function add( $start = 0 ){
$sum = $start;
//該函數接受n個值..執行後返回值爲n值和$sum的總和
return function () use ( &$sum ){
//獲取全部參數
$args = func_get_args();
for( $i = 0; $i < count($args); $i++ ){
$sum += (int)$args[$i];
}
return $sum;
};
}
//初始化值爲1
$add = add( 1 );
//在初始化值的基礎上加上1 2 3
$add( 1, 2, 3 );
//再加一次3 輸出
echo $add( 3 ); //10
?> 這段代碼的做用是 每調用一次add函數都會生成一個相應的$sum 每一個函數執行後不衝突 可避免使用static變量 並且sum不會隨函數執行結束而消失 從而實現函數柯里化
閉包的使用
1. 函數柯里化
閉包在js中常常會被用過函數柯里化
好比上面php的那段代碼中 改爲js則是:
//add函數 返回一個匿名函數
function add( start ){
var sum = start || 0;
//該函數接受n個參數 返回值爲n個參數的和+sum的值
return function(){
for( var i = 0, j = arguments.length; i < j; i++ ){
sum += Number( arguments[i] );
}
return sum;
}
}
var a = add( 1 );
a( 1, 2, 3 );
console.log( a( 3 ) );
玩個有意思的函數 這個是別人曾經給我出的一道題目 當時我也沒想出來...(壓根把tostring這方法給忘了.)
題目需求要求能夠這樣調用(當時的需求只要求傳一個參數)
//獲取curry後的函數
var a = add( 1 );
//調用屢次相加
a( 1, 2, 3 )( 1, 2, 3 )( 1, 2, 3 );
//直接輸出函數
console.log( a ); //19
//繼續相加後輸出
console.log( a( 1, 2, 3 )( 1, 2, 3 ) ); //31
實現以下
//add函數 返回一個匿名函數
function add( start ){
var sum = start || 0;
//該函數接受n個參數 返回值爲函數自己
//直接輸出函數時 打印sum的值
return function(){
//參數相加
for( var i = 0, j = arguments.length; i < j; i++ ){
sum += Number( arguments[i] );
}
//獲取函數自己
var func = arguments.callee;
//重寫函數tostring方法 用於打印函數
func.toString = function(){
return sum;
};
//返回函數自己
return func;
}
}
2。模擬對象中的私有屬性和方法
寫以前先解釋下 js非一門OO語言 它是一門基於對象的語言
如 var i = 0; 則i是一個數值型對象 轉成對象寫法則是 var i = new Number(1); 前一種叫過直接量表示法 同JSON(js對象字面量,表示js中對象的直接量表示方法) 直接量表示的速度要比 new 快
(1)模擬私有屬性和私有方法
//smarty模板引擎 模擬
function Smarty(){
//公有屬性 可被外部直接訪問
//左標籤
this.leftLimiter = '{';
//右標籤
this.rightLimiter = '}';
//私有屬性 不可被外部直接訪問
//緩存assign方法調用後的賦值
var cacheData = {};
//公用方法 assign
//準確來說..它叫作一個特權方法 可訪問內部私有屬性的方法叫作特權方法
//實例化smarty構造函數時 因爲它是一個公用方法 可被外部訪問
//而且引用了cacheData臨時變量 因此cacheData不會垃圾回收 此時生成一個閉包
this.assign = function( name, value ){
//緩存賦值
cacheData[name] = value;
}
//私有方法 fetch 編譯解析模板內容 返回結果 不輸出
//假設它是一個私有方法 不能被外部直接訪問
function fetch( tpl ){
//do something
return tpl;
}
//公用方法 輸出
this.display = function( tpl ){
//調用內部私有方法 直接輸出
console.log( fetch( tpl ) );
}
}
//實例化smarty
var template = new Smarty();
//設置左標籤
template.leftLimiter = '<{';
//設置右標籤
template.rightLimiter = '}>';
//賦值
template.assign( 'name', 'jsyczhanghao' );
//賦值
template.assign( 'age', 23 );
//輸出最終編譯結果
template.display( document.getElementById( 'test' ).innerHTML );
(2)模擬私有靜態方法(單例模式-Zend framework 模擬前端控制器 phper你懂的..)
//模擬Zend framework 前端控制器
//定義一個匿名函數 定義完當即執行(function( window ){
//Zend_Controller主構造函數 //在js中沒法設置私有的構造函數
//因此必須將構造函數設置爲 非公開 才能夠不讓外部調用的程序直接實例化構造函數 在公開對象中提供一個公開方法 間接去調用
var Zend_Controller = function(){
//設置控制器的路徑
this.setControllerDirectory = function(){};
//分發路由
this.dispatch = function(){
console.log( 1 );
};
};
//前端控制器的私有靜態屬性 外部不可直接訪問
//它爲一個Zend_Controller的實例
var intance;
//公開類 前端控制器
var Zend_Controller_Front = function(){};
//獲取實例 一個共有靜態方法
//可被外部調用的方法 生成閉包 臨時變量instance和Zend_Controller不會消失
Zend_Controller_Front.getInstance = function(){
//返回若是已存在實例 則直接返回
//不然 先建立再返回
return instance || ( instance = new Zend_Controller() );
};
//實際的js中習慣會把單例模式會這麼寫
//將Zend_Controller_Front直接寫成一個對象 getinstance天然就成了一個公用方法 可直接調用
//window.Zend_Controller_Front = {
// getInstance: function(){
// return instance || ( instance = new Zend_Controller() );
// }
//};
window.Zend_Controller_Front = Zend_Controller_Front;
})( this );
var zend_instance = Zend_Controller_Front.getInstance();
zend_instance.setControllerDirectory( '/root' );
zend_instance.dispatch();
3。事件回調函數中的使用
//更新元素內容 ajax
//第一個參數爲dom元素
//第二個參數發送的url
function updateElement( elem, url ){
//jquery中ajax的get方法
//在 #js的異步機制和大數據量的處理方案# 中有說到
//實際上在get方法事後...該函數已執行後
//get方法第2個參數的匿名函數 將會被丟到 UI隊列的最後面等待合適的機會觸發
//該機會就是ajax成功發送而且成功返回狀態值時觸發
//因爲匿名函數並不是當即執行 且依賴於elem參數 因此elem不會被當垃圾進行回收 並在今生成一個閉包
//必須等到 匿名函數成功執行後纔會被釋放..
$.get( url, function( data ){
//ajax發送成功後 將返回的值 寫到元素中
elem.innerHTML = data;
});
} 以上是閉包絕大部分會出現的場景
#############################################################################################################
來看個問題吧:針對 #js的異步機制和大數據量的處理方案# 中的一段代碼段
for( var i = 0; i < 10; i++ ){
//爲test0-test9綁定click事件
document.getElementById( 'test' + i ).onclick = function(){
//打印對應的i
console.log( i );
};
}
這段代碼執行後 點擊test0-test9並不是象預期那樣.. 依次打印出0-9 而是每個元素點擊後都打印了10
形成的緣由就是 綁定click事件時 回調函數並未執行 當回調函數執行時 i已經變成了10 因此打印的結果都會變成10
解決方法:
思路: 若是能找到一種方式能夠將每一次的i都緩存起來 而且一直到click事件觸發的時候 它都一直不會消失 不就完了麼
咱們都知道 一個函數做用域內執行完後..做用域中的全部臨時變量都會消失 可是有一種不讓臨時變量消失的方式就是使用閉包。。而上面講閉包的使用場景時 其中有一條就是事件回調函數 當一個事件回調函數位於一個做用域內的時候...做用域執行外後 因爲回調函數並未立刻執行..而是等到相應事件觸發時才執行...當回調函數依賴該做用域內的臨時變量時...致使該做用域內部使用的臨時變量沒法立刻被當垃圾回收(意味着該臨時變量不會消失)
目前咱們擁有一個事件回調函數 要作的就是須要讓這個事件回調函數位於一個函數做用域內
代碼:
for( var i = 0; i < 10; i++ ){
//爲test0-test9綁定click事件
function(){
document.getElementById( 'test' + i ).onclick = function(){
//打印對應的i
console.log( i );
};
};
}
這樣 事件綁定就位於一個匿名函數中了...可是這樣確定不行...由於函數都沒有執行...函數內的代碼確定不會起做用....也就是說..這段代碼可以正常執行 不報錯..可是不會爲每個元素綁定一個事件..由於它的外部函數沒有執行
繼續修改:
for( var i = 0; i < 10; i++ ){
//爲test0-test9綁定click事件
(function(){
document.getElementById( 'test' + i ).onclick = function(){
//打印對應的i
console.log( i );
};
})();
}
恩 此次看起來差很少了....綁定事件的行爲位於一個匿名函數中..而且匿名函數定義後當即執行....
可是目前 綁定事件內的變量i並非 匿名函數中所產生的臨時變量 i是一個全局變量 i不會由於匿名函數的執行而一直保持 你所但願的值
因此咱們須要在匿名函數內定義一個臨時變量 該臨時變量的值和當前相應的i值相等便可 將i直接賦值給該臨時變量就能夠了..
最終修改代碼:
for( var i = 0; i < 10; i++ ){ //爲test0-test9綁定click事件 (function(){ var j = i; document.getElementById( 'test' + j ).onclick = function(){ //打印對應的i console.log( j ); }; })();}其實不必定要直接賦值 當一個參數傳進去也行代碼以下(執行結果同樣..過程也沒什麼區別..只是寫法不一樣)for( var i = 0; i < 10; i++ ){ //爲test0-test9綁定click事件 (function( j ){ document.getElementById( 'test' + j ).onclick = function(){ //打印對應的i console.log( j ); }; })( i );}其實還有一種不使用閉包的方式...在事件的回調函數中直接引用 dom對象的一個屬性便可 由於dom對象是一直存在的 而指向當前的dom對象使用this便可for( var i = 0; i < 10; i++ ){ //爲test0-test9綁定click事件 var elem = document.getElementById( 'test' + i ); elem.index = i; elem.onclick = function(){ //打印對應的i console.log( this.index ); }; }