首先说明下...闭包是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 ); }; }