什么是闭包?
javascript和其他编程语言一样,也采用词法作用域,也就是说,函数的执行依赖于变量作用域.js函数对象的内部状态不仅包含函数的代密码逻辑,还必须引用当前的作用域。
函数对象可以通过作用域链相互关联起来,函数体内部的变量都可以保存在函数作用域内,这种特性在计算机科学文献中称为“闭包”。
这就是一个闭包,它可以在外部通过某一种手段(方法)可以访问到内部的变量:
<script type="text/javascript"> function fun1(){ var a = 100; function fun2(){ console.log(a); } return fun2; } var fun = fun1(); fun() </script>
闭包的特点:
1)占内存:当内部的函数被保存到了外面,就会形成闭包,闭包导致原有的函数执行完成了以后作用域链不会得到释放
造成了内存的泄露
2)保护私有变量的安全
闭包的应用:
1)计数器:
由于每次访问的都是同一个作用域,在函数掉用完一次之后并没有被回,反而更新了它的作用域,所以函数每调用一次,它的值就会更新,次数就会增加一次。
function add(){
var num = 0;
return function(){
num++;
console.log(num)
}
}
var fun = add();
fun() //1
fun() //2
2)做缓存结构
每调用一次jia函数它就会加一,每调用一次jian函数它就会减一,
function fun(){
var num = 0;
function jia(){
num++;
console.log(num);
}
function jian(){
num--;
console.log(num)
}
return [jia,jian];
}
var jia = fun()[0];
var jian = fun()[1];
jia() //1
jian() //-1
jia() //2
jian() //-2
3)封装
实例
利用闭包,解决循环添加事件遇到的作用域问题:
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title></title> </head> <body> <ul> <li>0</li> <li>1</li> <li>2</li> <li>3</li> <li>4</li> <li>5</li> <li>6</li> <li>7</li> <li>8</li> <li>9</li> </ul> <script type="text/javascript"> var lis = document.getElementsByTagName("li"); for(var i = 0;i < lis.length;i++){ lis[i].onclick = function(){ console.log(i) } /*(function(i){ lis[i].onclick = function(){ console.log(i) } })(i)*/ }
</script>
</body>
</html>
如上代码,获取li元素,然后为每个li循环添加一个单击事件,使单击同的li显示对应的类数组的索引。
但实际上,却是这种情况:(如图)
单击不同的li,控制台显示的都是10。
这是因为,函数作用域的问题:
1)先生成一个GO
2)预编译时
GO = {
lis : undefined
i : undefined
}
3)执行后
GO = {
lis : {}
i : 10
}
4)函数调用后生成10个AO
lis[i].AO(i = 0-9){ *10
}
此时在lis[i]函数中并没有找到i,所以只能向上级寻找,所以找到上级的i为10。这也就是每次单击时输出都为10的原因了。
现在我们用闭包来解决这个问题,把上面的那种方式注释,将其放到一个立即执行函数中,就形成了一个闭包。
<script type="text/javascript"> var lis = document.getElementsByTagName("li"); for(var i = 0;i < lis.length;i++){ // lis[i].onclick = function(){ // console.log(i) // } (function(i){ //ec6之前解决的方法,有且只有这一种 lis[i].onclick = function(){ console.log(i) } })(i) } </scrip>
此时我们单击时就可以获取正确的索引值。
此时的生成的GO是同之前的GO 相同的,只是在执行立即函数时分别生成了10个AO,
{
i : 0 - 9
}
当再次触发单击事件时,单击函数寻找的对象就成了父级的AO的i,即立即执行函数的10个AO中的i。所以单击第i个就会寻找到与之对应的i的值,也就是不同li的索引值。