javascript闭包,你大爷永远是你大爷
闭包 what 's the fuck
闭包,是什么鬼,谁学谁都脑进水!
我会用5W3H1TS的方式,去讲闭包
1. what
一个函数,记住自己定义时的“词法作用域”,就产生了闭包
即使此函数在其他地方执行!
上一个“词法作用域”的 例子 ,程序走起,与JAVA的动态作用域,有本质不同
因果链思维中,这个词法作用域就是“已知的因为”,通过这个因为,我们往下想,才能想通,闭包这个结果
例子:
var fn1;
function aa(){
var t1 = 100;
fn1 = function(){
console.log(t1);
}
}
var t1 = 200;
aa();
结果是打印 100,
结论: 可以确定,JS真的是词法作用域
我是按“例子-结论”的思维去做的
2. why
闭包是解决函数式语言的一个问题的一种技术,这个问题就是如何保证将函数当做值创造并传来传去的时候函数仍能正确运行。
闭包实现的例子,其中一种用法: 模拟块作用域,不要全局污染。因为JS只有全局作用域和函数作用域(这话并不完全对,但先记住,这就是JS让人FUCK的原因。。。,不像JAVA,非常规矩,让学的人,学起来也能非常清楚,省力)
3. who
JS有的,我们这里只讨论JS的范畴,宿主环境,JS的引擎。其他语言,有的有,有的没有
4.where
框架中,还有立即执行函数中,JQUERY库,JQ写的小组件
5. when
看下面链接的文章
6. how1
具体用时,要写什么,先上一个小例子
例子:
var result = foo(1)(2); alert(result); function foo(a){ return function(b){ return a+b; } }
很简单,结果为3
再上一个有应用场景的小例子
<html>
<title>小例子</title>
<meta http-equiv="Content-Type" content="text/html" ; charset="utf-8">
<body onload="myEffect()">
<table id="mytab" border="1">
<tr>
<td>
第0行
</td>
</tr>
<tr>
<td>
第1行
</td>
</tr>
<tr>
<td>
第2行
</td>
</tr>
</table>
<div id="console" style="background:#ffff00"></div>
</body>
<script>
function myEffect(){
var console=document.getElementById('console');
var tab=document.getElementById('mytab');
var trs=tab.getElementsByTagName('tr');
for(var i=0;i<trs.length;i++){
trs[i].onclick=(function(){
var rowNum=i;
return function(){
console.innerHTML="点击了第"+rowNum+"行";
}
})();
}
}
</script>
</html>
试试,不去用 var rowNum=i;
而是改为:
trs[i].onclick=(function(){
return function(){
console.innerHTML="点击了第"++"行";
}
})();
会发生神马,然后自行体会一下,再往下看
7.how2
实现原理
函数调用是用栈,存变量是用堆(不一定正确,只是比喻,为了方便理解),
函数被调用时,函数
与内存GC机制相关,所以3时说,闭包,在其他语言中,有的有,有的没有
一般来说,一个函数在执行开始的时候,会给其中定义的变量划分内存空间保存,以备后面的语句所用,等到函数执行完毕返回了,这些变量就被认为是无用的了.对应的内存空间也就被回收了.下次再执行此函数的时候,所有的变量又回到最初的状态,重新赋值使用.
但是如果这个函数内部又嵌套了另一个函数,而这个函数是有可能在外部被调用到的.并且这个内部函数又使用了外部函数的某些变量的话.这种内存回收机制就会出现问题.如果在外部函数返回后,又直接调用了内部函数,那么内部函数就无法读取到他所需要的外部函数中变量的值了.所以js解释器在遇到函数定义的时候,会自动把函数和他可能使用的变量(包括本地变量和父级和祖先级函数的变量(*变量))一起保存起来.也就是构建一个闭包,这些变量将不会被内存回收器所回收,只有当内部的函数不可能被调用以后(例如被删除了,或者没有了指针),才会销毁这个闭包,而没有任何一个闭包引用的变量才会被下一次内存回收启动时所回收.
这一段,即是原理,也是实现,(更是核心关键,理解了这个,就理解了闭包。)
用大白话,第一人称的说话法是,我函数是大爷,我函数在定义时,所用的变量,你必须都给我找地方存好了,就存一份就行,爷被调用时候,就要用这些变量!记做:你大爷永远是你大爷
函数的参数,在这个函数每次被调用之时,参数也是开出一个内存空间去存的
看个TIME的例子,也是核心例子了!
for(var i = 0; i < 5; i++) {
setTimeout(function() {
console.log(i);
}, 1); //1毫秒后,打印
}
这个代码,本意是想 打印
0
1
2
3
4
但是,打出来是
5
5
5
5
5
当你知道“你大爷永远是你大爷”的理论后,就会明白,
function() {
console.log(i);
} 此函数,所用的i这个变量,是存了一份,找地方放好了,但是这个变量i,他的值却也在变化,所以当此函数用这个变量时,这个变量的值,就变为5了!
要想达到输出,0到4的结果,就要让变量存上一份,并且没有其他程序去改变他,就OK了
下面,提供四个改法,都是用了上面的理论,大家再体会一下
for(var i = 0; i < 5; i++) {
setTimeout((function(ii) {
return function(){
console.log(ii);
}
})(i), 1);
}
这是setTimeout内部的函数,去闭包存一份不变的变量法,还可以不用参数去存一份单独的变量
for(var i = 0; i < 5; i++) {
setTimeout((function() {
var ii = i;
return function(){
console.log(ii);
}
})(), 1);
}
我们再来,把单存的变量,放到setTimeout之外
for(var i = 0; i < 5; i++) {
(function(ii) {
setTimeout(function () {
console.log(ii);
}, 1);
})(i);
}
同理,也可以不用参数:
for(var i = 0; i < 5; i++) {
(function() {
var ii = i;
setTimeout(function () {
console.log(ii);
}, 1);
})();
}
这就是四个例子,核心思想就是让闭包的变量是单独的,不要被别的代码去改变值,这样就能达到想要的目的了,其实就是在内存中建了5个变量,去存这0,1,2,3,4这5个变量的值,在需要的时候,取出来用。
8.tip
优点:
1.保护函数内的变量安全,加强了封装性
2.在内存中维持一个变量(用的太多就变成了缺点,占内存)
缺点
闭包的缺点就是常驻内存,会增大内存使用量,内存浪费,使用不当很容易造成内存泄露,无效内存的产生
9.scene
为了看 复杂的代码时用,自己主动用得少,
藏于框架之中的有,交互响应,写的函数中,传的参数常量,就是闭包,但一般人就当黑盒子看,对使用框架的人来说,是透明的
id.onclick = function(){ alert(i); } 循环四次,打出来,都是4
用闭包,就搞定了
PS : 沸水理论,简版,一壶水,只有烧到100度才能去喝,你每次都只烧到80度就不烧了,那么你永远也喝不上这水,学习也是一样,快要学透闭包了,就停下来不学,那么永远也不可能真正撑握闭包,不可能在工作中,随心所欲的去使用闭包。
学到此,你应该已经是沸水100度了!
写的不好,请大家多批评:)
下一篇: 设计模式之单例模式