前言
在JS中,闭包和异步并称两大难点,那么闭包到底是什么,闭包有什么作用?接下来将通过几个案例来对闭包加以理解。
变量作用域
在讲闭包之前,先来看一下变量作用域。变量根据作用域的不同分为两种:全局变量和局部变量。主要有一下几点:
- 在函数内部变量的称为局部变量,在函数外部的则称为全局变量。
- 在函数内部可以使用全局变量,而在函数外部不能使用局部变量。
- 当函数执行完毕,本作用域内的局部变量会立即销毁
什么是闭包?
《JavaScript高级程序设计》中将闭包(closure)定义为:有权访问另一个函数作用域中变量的函数。即:闭包是函数
可以简单理解为: 若果一个函数内部有一个局部变量,别的全局作用域或局部作用域可以访问到这个变量,就会产生闭包,这个被访问局部变量所在的函数则称为闭包函数。
举个例子:
function fn() {
var num = 10;
function fun() {
console.log(num);
}
fun();
}
fn(); //10
上述代码执行过程中,函数fun的作用域访问了另外一个函数fn作用域中的局部变量num,此时便产生了闭包。可以从chrom浏览器断点调试窗口看见生成的闭包fn 。
现在将以上代码稍作修改:
function fn() {
var num = 10;
return function() {
console.log(num);
}
}
var f = fn(); //10
f();
执行过程相当于:
var f = function() {
console.log(num);
} //此实f是一个函数,里面存的是一个匿名函数。
f(); //10
上述执行过程中,在fn作用域之外的函数访问了fn作用域内的局部变量num,产生了闭包,闭包函数仍旧是fn。
闭包有什么作用?
正常情况下,一个函数内的变量只能在本函数内使用,且函数执行完毕后局部变量回立即销毁。
但在上述代码中,fn中的局部变量num在其作用域外部的f函数也可以使用,且fn()执行完毕后num并没有立即销毁,而是等f()也执行完毕后才销毁。
所以不难得出结论:闭包可以延伸变的量作用范围。
闭包案例
案例一:点击li标签输时输出当前li对应的索引值。
<nav>
<ul>
<li>苹果</li>
<li>香蕉</li>
<li>葡萄</li>
<li>菠萝</li>
</ul>
</nav>
<script>
var lis = document.querySelectorAll('li');
for(var i = 0; i < lis.length; i++) {
lis[i].onclick = function() {
console.log(i);
}
}
</script>
上述代码执行后,会发现无论点击那个li标签,输出的索引值均为4,这是因为onclick中的函数为异步任务,要点击时才会触发,而for循环执行时会直接跳过onclick中的函数。
现将其结合立即执行函数即可实现:
<script>
for(var i = 0; i < lis.length; i++) {
(function(i) {
lis[i].onclick = function() {
console.log(i);
}
})(i);
}
</script>
在修改后的代码中,每次循环都会产生一个立即执行函数,点击后onclick中的函数访问立即执行函数接收的变量i,输出当前索引值,这一过程中便产生了闭包,可以推断这个闭包为立即执行函数。立即执行函数也称为小闭包,因为该函数内的任意一个函数都可以使用它传递的参数。
但是会发现,上述代码在每次循环中都会产生一个立即执行函数,会降低效率,同时,立即执行函数中的变量i并没有在函数执行完毕后就立即销毁,而是要等onclick中的函数执行完毕后才会销毁,倘若一直不点击,则会导致内存泄露。使用ES6中新增的let属性实现该功能会更加理想。因此,在实际开发中要结合实际情况判断是否使用闭包。
案例二:点击li标签,5秒之后输出所有li元素
for (var i = 0; i < lis.length; i++) {
(function(i){
setTimeout(function () {
console.log(lis[i].innerHTML);
},5000);
})(i);
}
此处定时器中的回调函数与案例一中onclick中的回调函数均为异步任务,只有在触发了才会执行,即5秒钟之后才会执行,而for循环时同步任务,会立即执行。因此需要将其放入立即执行函数中。
这一过程中,定时器中的函数的作用域访问了立即执行函数中的i变量,产生了闭包。
思考题
可以思考一下以下代码的执行结果及其原因,并判断是否有闭包产生
1.
var name = 'this is window';
var obj = {
name: "this is Obj",
getNameFunc: function() {
return function() {
return this.name
}
}
}
console.log(obj.getNameFunc()()); //this is window
提示: 全局变量时挂载在window下的,且匿名函数的this指向window
2.
var name = 'this is window';
var that;
var obj = {
name: "this is Obj",
getNameFunc: function() {
that = this;
return function() {
return that.name
}
}
}
console.log(obj.getNameFunc()()); //this is Obj
总结:
- 闭包是函数。一个作用域可以访问另外一个函数的局部变量,被访问变量所在的函数即是闭包函数。
- 闭包延伸了变量作用域
- 开发中应结合实际情况决定是否使用闭包