先来说一下JavaScript中的变量作用域
变量作用域分为:局部变量和全局变量。
Javascript中的局部变量只存在于函数中,在函数外部是无法访问的。但是函数内部是可以访问全局变量的
var a = 10
function test () {
var b = 20
console.log(b) // b=20
console.log(a) // a=10
}
console.log(a) // a=10
console.log(b) //b is not defined
那么如何能从外部获取函数内的局部变量呢?
那就是在函数内部再定义一个函数
function f1(){
var a = 10
function f2(){
console.log(a) //a是可以被访问到的
}
}
上面的代码中,f1函数内部定义了一个f2函数,f2可以访问f1里的所有变量,反之f1不能访问f2中的变量。这里需要提到一个js的一个概念叫做链式作用域,子对象会一级一级的向上寻找所有父对象的变量,所以子对象可以访问父对象的所有变量,反之父对像不能访问子对象的变量。
现在f2可以访问f1里的值了,那么我们把f2函数返回出来不就可以在f1函数外部访问它里面的变量了么
function f1(){
var a = 10
return function f2(){
console.log(a)
}
}
var result = f1()
result() // a = 10
至此,我们把f2叫做闭包(闭包本质上将函数内部和函数外部连接起来的一座桥梁)
概念:
在一个函数里面嵌套另一个函数,被嵌套的那个函数的作用域就是一个闭包。
作用:可以操作局部作用域的变量,变量不会被浏览器回收,保存变量的值。
应用
1.操作局部作用域的变量
<script type="text/javascript">
function outerFun () {
var num = 2;
//定义一个内部函数
function innerFun () {
//内部函数的返回值是外部函数的一个局部变量
return num;
}
//把局部变量的值++
num++;
// 返回内部函数
return innerFun;
}
var num = outerFun()(); // 3
alert(num);
</script>
上例中虽然函数的声明在num++之前,但是函数返回的时候num已经++过了,所以最后的结果是num++之后的值。即闭包中使用的局部变量的值,一定是局部变量最后的值。
function f1(){
var n=999;
nAdd=function(){n+=1}
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
nAdd();
result(); // 1000
在这段代码中,result实际上就是闭包f2函数。它一共运行了两次,第一次的值是999,第二次的值是1000。这证明了,函数f1中的局部变量n一直保存在内存中,并没有在f1调用后被自动清除。
为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是”nAdd=function(){n+=1}”这一行,首先在nAdd前面没有使用var关键字,因此nAdd是一个全局变量,而不是局部变量。其次,nAdd的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以nAdd相当于是一个setter,可以在函数外部对函数内部的局部变量进行操作。
使用闭包的问题
1、闭包会使函数的变量保存在内存中,内存消耗过大所以不能滥用 会导致网页卡顿等问题。
2、闭包中父函数中变量值的改变,会影响子函数中的引用的变量值。如下面的for循环问题。
for循环的问题
<body>
<input type="button" value="按钮1" >
<input type="button" value="按钮2" >
<input type="button" value="按钮3" >
<script type="text/javascript">
var btns = document.getElementsByTagName("input");
for (var i = 0; i < 3; i++) {
btns[i].onclick = function () {
alert("我是第" + (i + 1) + "个按钮");
};
}
</script>
</body>
上面的代码在点击三个按钮的时候都是弹出‘我是第四个按钮’,这个错误的原因就是闭包导致的,每循环一次就会有一个匿名函数设置点击事件,闭包总是保持的变量的最后一个值,所以点击的时候,总是读的是i的最后一个值4
解决办法1
我们可以给每个按钮添加一个属性,来保存每次i的临时值
<body>
<input type="button" value="按钮1" >
<input type="button" value="按钮2" >
<input type="button" value="按钮3" >
<script type="text/javascript">
var btns = document.getElementsByTagName("input");
for (var i = 0; i < 3; i++) {
//把i的值绑定到按钮的一个属性上,那么以后i的值就和index的值没有关系了。
btns[i].index = i;
btns[i].onclick = function () {
alert("我是第" + (this.index + 1) + "个按钮");
};
}
</script>
</body>
解决办法2
或者使用匿名函数的自执行
<body>
<input type="button" value="按钮1" >
<input type="button" value="按钮2" >
<input type="button" value="按钮3" >
<script type="text/javascript">
var btns = document.getElementsByTagName("input");
for (var i = 0; i < 3; i++) {
//因为匿名函数已经执行了,所以会把 i 的值传入到num中,注意是i的值,所以num
(function(num){
btns[i].onclick = function(){
alert("我是第" + (num + 1) + "个按钮")
}
})(i)
}
</script>
</body>
参考链接
http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html
https://segmentfault.com/q/1010000011444538