JavaScript闭包的理解
一、闭包的概念:闭包就是能够读取其他函数内部变量的函数。例如在javascript中,只有函数内部的子函数才能读取局部变量,所以闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
当然,以上解释来自百度百科,对于一些人来说可能看了还是蒙蒙的,接下来看一下闭包是怎么形成的
二、闭包的形成
(1)在认识闭包对全局变量和局部变量进行理解:
var f1 = 2;
function fn1(){
var f2 = 3;
function fn2(){
var f = f1 + f2;
}
fn2();
}
fn1();
顾名思义,全局就好比我们生活的区域一样,生活中的公共区域就好比全局,我们各自的家就好比是局部
在以上代码中,可以看到fn1的定义和执行都在全局,fn2的定义和执行是在fn1的内部,f1就是在全局声明的,f2是在函数fn1的局部声明的,而f这是在fn2的局部声明的。
(2)现在对全局和局部有了了解后,再做一个小测试:
function fn1(){
var a = 3;
var num = 0;
num += a;
console.log(num);
}
fn1();
fn1();
fn1();
可以看到,每次拿到的num都是3,这是因为,每次执行fn的时候都重新声明了a和num。如果想每次拿到的num都加上3怎么做呢?
(3)那么就让a和num只声明一次,把a和num放在全局声明,再局部只是作值的改变,请看下面的改变:
var a = 3;
var num = 0;
function fn1(){
num += a;
console.log(num);
}
fn1();fn1();fn1();
输出结果:
这样就拿到的num就在原基础上加3。
(4)对上面的代码封装,把函数fn2返回出来,并执行,依然可以拿到3,6,9
function fn1(){
var a = 3;
var num = 0;
return function fn2(){
num += a;
console.log(num);
}
}
var f = fn1();
f();f();f();
输出结果:
接下来做一点优化,把函数fn1放在匿名函数中自动执行,因为匿名函数自动执行,f();就是执行了函数fn2。
var f = (function fn1(){
var a = 3;
var num = 0;
return function fn2(){
num += a;
console.log(num);
}
})();
f();
f();
f();
输出结果:
以上就是一个简单闭包的演化过程。
简单的说闭包就是利用函数的嵌套,在外部函数外面控制外部函数的局部变量。
闭包的开始和结束:
var f = (function fn1(){
var a = 3;
var num = 0;
return function fn2(){ // 闭包开始
num += a;
console.log(num);
}
})();
f();
f = null; // 闭包结束
f();
输出结果:
三、闭包的特点
(1)闭包可以让全局变量私有化:
下面的a和num变量是全局变量:如代码1 私有化后:如代码2
(2)可以在函数的外部修改函数内部的变量:
可以看到以上代码中,函数fn2对函数fn1的局部变量a和num进行了操作
(3)作用域不销毁:
是因为执行完外部函数fn1后,函数fn2保存了fn1的局部变量,导致fn1执行结束后没有立即释放内存,所以作用域没有销毁。
四、闭包应用:
(1)可以在循环的点击事件里面获取到指定的索引
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<style>
div{width:100px;height: 100px;background-color: #f00;margin: 10px;}
</style>
<body>
<div class ="box1">1</div>
<div class ="box2">2</div>
<div class ="box3">3</div>
<div class ="box4">4</div>
</body>
<script>
var abox = document.querySelectorAll("div");
for(var i = 0;i < abox.length;i++){
abox[i].onclick = function(){
console.log(i);
}
}
</script>
</html>
输出结果:
可以看到每次点击后,得到的i都是最后一个索引,这是因为循环是一瞬间的,当点击的时候,循环已经结束了。
于是可以利用闭包的特点拿到每个索引:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<style>
div{width:100px;height: 100px;background-color: #f00;margin: 10px;}
</style>
<body>
<div class ="box1">1</div>
<div class ="box2">2</div>
<div class ="box3">3</div>
<div class ="box4">4</div>
</body>
<script>
var abox = document.querySelectorAll("div");
for(var i = 0;i < abox.length;i++){
(abox[i].onclick = function(i){
console.log(i);
})(i);
}
</script>
</html>
输出结果:
以上代码也可以使用ES6的新增关键字let解决,因为let内部实现了闭包机制。
(2)闭包实现代码的模块化:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<style>
</style>
<body>
</body>
<script>
var f = (function fn(){
return{
show:function(q){
console.log("方法一"+q);
},
say:function(q){
console.log("方法二"+q);
}
}
})();
f.show(2);
f.say(5);
</script>
</html>
输出结果:
总结:闭包虽然解决了全局变量,但是大量的使用闭包,会消耗内存,甚至可能导致内存泄漏,所有要按需使用。以上只是个人对闭包的简单描述,如有不正确的地方,希望读者可以指出改正,疫情期间,逆战加油!