javascript 闭包
名词解释
官方的解释可以无视了,比绕口令还要绕口令的拗口的词汇拼接。
个人理解就是在函数中嵌套函数。
前提
理解闭包必须先理解另外另个内容:1.作用域链 2.垃圾回收
作用域链
js中有两种变量,一种叫局部变量,一种叫全局变量。
没有写在function内部的都是全局变量,写在function内部的都是局部变量,为了避免自己定义的变量名污染全局变量名(尤其是现在调用的js控件越来越多的年代),因此很少会定义全局变量。
因为局部变量就是声明在function内部的变量,因此这个function就是该变量的作用域。也就是这个function内部,包括这个function的儿子(子函数)、孙子(子函数内的子函数),均可以访问到。这也就是说,这个function内部的子函数也可以访问到这个变量。
var f = function(){
// f就是a的作用域
var a = 999;
function f1(){
// 因此,即使在f的内部嵌套函数(子函数)中也可以访问到a
alert(a);
}
f1();
};
f();
垃圾回收机制
在函数执行的时候,js的解释器会给所有需要用到的变量分配一个内存空间,这样一直到这个函数执行结束,就会认为这些变量已经没有用了,就会启用垃圾回收机制来把这些内存回收。
这样在遇到函数嵌套时,如上面例子所示,在f执行完成之后,变量a因为还要被f1这个内部嵌套函数继续使用,因此a并不会被释放,因此,在f1()的结果是999
也就是说,在js解析器发现函数中嵌套了函数时,就会把函数中的变量和子函数中的变量一起保存在内存中,也就是构建了一个“闭包”。这些变量不会被内存回收器回收,只有当内部嵌套函数不再执行之后,才会被回收。
闭包案例
属性
var person = function(){
var name = "张三";
this.getName=function(){
return name;
};
};
var p = new person();
alert(p.getName());
上面的代码里,name这个属性只能通过调用getName这个方法才能获取,这是必包的用法之一。
保存复杂计算中的临时变量
经典案例:
html:
<ul>
<li>aaa</li>
<li>bbb</li>
<li>ccc</li>
<li>ddd</li>
想要实现的效果是点击li的时候,alert各自的内容,
比如点击第一个,alert “aaa”
很多人第一反应的代码如下:
var foo = function(){
var lis = document.getElementsByTagName("li");
for (var i= 0;i<lis.length;i++){
lis[i].onclick = function(){
alert(i);
};
}
};
foo();
然后会发现结果和想象中完全不一样,点击任何一个都是返回4。
这是因为在赋值的时候,传的i是对内存地址对引用。当循环结束后,i指向对就是4.因此,所有的li在点击时都是alert(4)。
在这个时候就要用到闭包了。在每一次循环的时候,都把当前的i通过立即执行函数赋值。
如下所示:
<script type="text/javascript">
"use strict"
var foo = function(){
var liList = document.getElementsByTagName("li"),
i=0,
max = liList.length;
for(;i<max;i++){
(function(index){
liList[index].onclick = function(){
alert(liList[index].innerHTML);
};
})(i);
}
};
foo();
</script>
用闭包实现Fibonacci数列
话不多说,直接上代码
var fibonacci = function(n){
// 将斐波那契数列放在这个临时变量中
var arr = [0,1,1];
var fi = function(m){
// 如果存在则直接返回
if(arr[m]){
return arr[m];
}else {
// 如果不存在则递归获取
arr[m] = fi(m-1)+fi(m-2);
return arr[m];
}
};
return fi(n);
};
alert(fibonacci(10));
上面的代码如果不用闭包来做,那么arr数组将不能一直保存在内存中,每次递归都会重置了。
当然,以上代码可以进一步优化:
var fibonacci = (function(){
var arr = [0,1,1];
return function(n){
if(arr[n]){
return arr[n];
} else {
arr[n] = fibonacci(n-1)+fibonacci(n-2);
return arr[n];
}
};
})();
内部嵌套函数返回这样一个匿名函数:
function(n){
if(arr[n]){
return arr[n];
} else {
arr[n] = fibonacci(n-1)+fibonacci(n-2);
return arr[n];
}
}
因此主函数要立即执行才能返回结果。
其他demo(闭包的高级应用)
12个按钮,显示"呜呜",按下哪个,变成"哈哈",其他继续变回"呜呜"
<script type="text/javascript">
"use strict"
var btns = document.getElementsByTagName("button"),
i=0,
btnLength = btns.length;
for(;i<btnLength;i++){
(function(k){
btns[i].onclick = function(){
this.innerHTML = "哈哈";
for(var j=0; j<btnLength;j++){
if(k!=j){
btns[j].innerHTML = "呜呜";
}
}
};
})(i);
}
</script>
上面这种写法虽然可行,但是每次点击都要循环12次,效率太低,可以进一步使用闭包。
<script type="text/javascript">
"use strict"
var load = function(){
var btns = document.getElementsByTagName("button"),
i=0,
btnLength = btns.length,
current;
for(;i<btnLength;i++){
(function(k){
btns[i].onclick = function(){
if(current){
current.innerHTML = "呜呜";
}
this.innerHTML = "哈哈";
current = this;
};
})(i);
}
};
window.onload = load();
</script>
这样写简直太巧妙了。。。
先在闭包中创建current这个变量,然后如果current存在,则说明这不是第一次点击了,那么current中保存的就是上一次点击的那个按钮。所以只要把上一次点击的按钮变回“呜呜”即可。
然后再把这次的先变为哈哈,再存到current中。