在说JavaScript中闭包概念之前,我们先来了解一下作用域和变量生命周期的问题。作用域在前面的博客中有提到,大家可以点击查看一下,关于变量的生命周期,什么是生命周期?也就是变量从创建到销毁的一个过程。对于全局变量,它的生命周期是永久的,除非我们在开发中主动销毁这个变量,而在函数中的局部变量,当退出该函数时,这个函数内的局部变量回随着函数的结束而被销毁。了解了作用域和生命周期的基本概念后,我们来说一下闭包。
什么是闭包?闭包就是指有权限访问另一个函数作用域内变量的函数,通常情况下就是一个函数中包含另一个函数。我们先来看一个有关闭包特点的经典案例,代码如下:
<div>0</div>
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
<script>
var divList = document.getElementsByTagName('div');
for (let index = 0; index < divList.length; index++) {
divList[index].onclick = function(){
console.log(i);
}
}
</script>
点击不同的div,获取对应的text,上面代码的实现结果却不随人愿,你会发现点击任意div,都是打印的5,问题来了:为啥打印的都是5呢?原因很简单,因为点击的onclick事件是异步触发的,当点击事件被触发时,for循环早已结束,此时变量i的值早已经是5,所以不管你点击哪一个,都会打印的是5。如何解决呢?这时候就可以用到闭包了,请看如下代码:
var divList = document.getElementsByTagName('div');
for (let index = 0; index < divList.length; index++) {
divList[index].onclick = (function (i) {
return function(){
console.log(i);
}
})(index)
}
上面的第二种方式,就是借助了闭包的特点,在for循环的时候,每次循环将index值封闭起来,当点击事件触发的时候,就会顺着当前的作用域链由内往外依次寻找变脸index,这时就会找到被封闭在闭包环境内的index值,这样点击对应的div时就会打印对应的text。
看到上面的例子,我们可以看出创建闭包的常见方式,就是在一个函数的内部再创建另一个函数,通过另一个函数访问这个局部变量,利用闭包的特性就可以突破作用域链的限制,就可以将函数内部的变量和方法暴露到外部,这样外部就可以访问函数内部的变量和方法,闭包实现的原理就是:
- 闭包自身的作用域链包含着自己的作用域,函数作用域以及全局作用域;
- 函数作用域和函数内的局部变量,会在函数执行后销毁;
- 当函数返回的是一个闭包时,这个函数的作用域会一直在内存中保存;
闭包的特点:
- 可以访问函数内部的变量和方法,保持函数在代码环境内一直存在,不会被垃圾回收机制处理;
- 函数内再嵌套函数,子级向父级查找变量,逐级查找;
闭包的缺点:
- 上面说到的原理中,闭包自身的作用域链包含着自己的作用域,函数作用域以及全局作用域,这也是它的缺点,由于它的这个特点,我们就需要去维护一些额外的作用域;
- 由于不会被垃圾回收机制处理,所以如果大量使用闭包的话,就会占用过多的内存;