在百度百科中,有闭包的解释。
【百度百科】官方对闭包的解释是:一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。
【百度百科】闭包的特点:
1.作为一个函数变量的一个引用,当函数返回时,其处于激活状态。
2.一个闭包就是当一个函数返回时,一个没有释放资源的栈区。
百度百科这么说有点绕,感觉意思也差不多,通俗地理解:
1、闭包就是一个封闭的包,它有对外(环境)变量的引用;
2、闭包一直存在于内存当中。
就这两点的理解就足够了,但由此可以有很多的应用,JavaScript语言的魅力也可以在此展现。
一个简单的例子就很能说明。
每篇CSDN的文章下面都有点赞按钮和踩的按钮,一般的写法是:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript中的闭包</title>
</head>
<body>
<label>点赞:</label><label id="lblPraise"></label>
<label>踩:</label><label id="lblTread"></label>
<br>
<button type="button" onclick="praise()">点赞</button>
<button type="button" onclick="tread()">踩</button>
<script>
praiseCount=0;//点赞的计数
treadCount=0;//踩的计数
function praise(){
praiseCount=praiseCount+1;
document.getElementById("lblPraise").innerHTML=praiseCount;
}
function tread(){
treadCount=treadCount+1;
document.getElementById("lblTread").innerHTML=treadCount;
}
</script>
</body>
</html>
这是可以实现的:
问题是这里声明了全局变量,容易造成变量污染和冲突,通常情况下是要避免的,那么要实现这个功能,应该怎样写呢?
把变量声明写在函数内行不行?如果这样的话,每次点击都会从0开始计数了,肯定不行。
这里就可以利用上面说的闭包的两个特点来实现。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JavaScript中的闭包</title>
</head>
<body>
<label>点赞:</label><label id="lblPraise"></label>
<label>踩:</label><label id="lblTread"></label>
<br>
<button type="button" onclick="btnPraise();console.log(praiseCount);">点赞</button>
<button type="button" onclick="btnTread();console.log(treadCount);">踩</button>
<script>
var btnPraise=(function(){
var praiseCount=0;//点赞的计数
return function praise(){
praiseCount=praiseCount+1;
document.getElementById("lblPraise").innerHTML=praiseCount;
};
})()
var btnTread=(function(){
var treadCount=0;//踩的计数
return function tread(){
treadCount=treadCount+1;
document.getElementById("lblTread").innerHTML=treadCount;
};
})()
</script>
</body>
</html>
实现效果:
这里的praiseCount和treadCount在外面是访问不了的,所以console.log(praiseCount);和console.log(treadCount);会报错。
这个例子就很明显地说明了闭包的作用和特点。
1、里面是可以访问外面的,但是外面访问里面必须通过外露的方法来实现;
2、闭包是驻留内存的,而一般函数如果没有引用执行完毕后就作为垃圾回收了。
这里需要弄清楚的是每个闭包都会牵扯到链式作用域的问题。
所谓的链式作用域就是在这个作用区域或者范围内,包裹在最内层的对象可以一层一层向上(或者说向外)访问父对象,也就是里面的对象可以访问外面的对象,但外面的对象是不能访问里面的对象。
通过例子可以很清楚地表现出这个概念。
var a=10;
x=((function (){
var a=20;
return function (){
var a=30;
return function (){
a=a+1;
return a;
};
};
})())();
console.log(x());//输出31
console.log(x());//输出32
console.log(x());//输出33
找到了var a=30,则直接运算后返回结果。
如果注释掉var a=30;结果变化是:
var a=10;
x=((function (){
var a=20;
return function (){
// var a=30;
return function (){
a=a+1;
return a;
};
};
})())();
console.log(x());//输出21
console.log(x());//输出22
console.log(x());//输出23
找到了var a=20,则直接运算后返回结果。
如果注释掉var a=30;和var a=20;结果变化是:
var a=10;
x=((function (){
// var a=20;
return function (){
// var a=30;
return function (){
a=a+1;
return a;
};
};
})())();
console.log(x());//输出11
console.log(x());//输出12
console.log(x());//输出13
找到了全局定义var a=10,则直接运算后返回结果。
上面的例子比较明显地显示了在闭包中的变量作用域链,也就是它会一层一层地寻找这个变量所对应的值,找到了就输出,不再继续寻找,如果一直到最外层也找不到则会报错。
这里有一个简单的例子,按钮点击改变标题的颜色:
效果图:
一般的做法可以是:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>点击变色</title>
</head>
<body>
<h1>点击按钮改变标题颜色</h1>
<button style="color:red" name="btnRed" onclick="changeColor(this)">变红</button>
<button style="color:blue" name="btnBlue" onclick="changeColor(this)">变蓝</button>
<button style="color:yellow" name="btnYellow" onclick="changeColor(this)">变黄</button>
<script>
function changeColor(btn) {
document.getElementsByTagName('h1')[0].style.color=btn.style.color;
}
</script>
</body>
</html>
有的可能喜欢使用事件赋值的方式,比如:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>点击变色</title>
</head>
<body>
<h1>点击按钮改变标题颜色</h1>
<button style="color:red" name="btnRed">变红</button>
<button style="color:blue" name="btnBlue">变蓝</button>
<button style="color:yellow" name="btnYellow">变黄</button>
<script>
var buttons = document.getElementsByTagName('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function () {
document.getElementsByTagName('h1')[0].style.color = buttons[i].style.color;
}
}
</script>
</body>
</html>
这就涉及到了闭包,因为在函数体内的i始终与外()里面的i是保持一致的,最后i的值就是3,程序执行起来就会报错。
修改的方式也简单,直接把var改成let就可以了,即:
var buttons = document.getElementsByTagName('button');
for (let i = 0; i < buttons.length; i++) {
buttons[i].onclick = function () {
document.getElementsByTagName('h1')[0].style.color = buttons[i].style.color;
}
}
或者这里也可以使用this,即:
var buttons = document.getElementsByTagName('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = function () {
document.getElementsByTagName('h1')[0].style.color = this.style.color;
}
}
或者这里也可以使用立即函数来写:
var buttons = document.getElementsByTagName('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].onclick = (function (i) {
return function () {
document.getElementsByTagName('h1')[0].style.color = buttons[i].style.color;
}
})(i);
}
需要注意的是闭包中变量的作用域链关键不在于什么地方定义和声明,看实际应用到的场合和场景。
利用闭包的这两个特点,在JavaScript面向对象的编程中可以有很多的灵活运用,比如对象的封装与继承、对外接口的定义、任务列表对象的迭代等等。