最近在写一些东西,需要用到闭包的一些知识,初听到这个概念的时候到网上搜了一些文章,大多数写的比较抽象,直到看到我心目中的牛人阮一峰写的一篇关于闭包的博客,从而豁然开朗,因此本文中也有一部分观点引用了阮一峰老师的。闭包是 JavaScript 中比较抽象的一个概念,很多初学者都没有很好的理解,在此将我所理解的闭包记录下来分享给大家,希望能对各位有所帮助。
在讲闭包之前,首先来说一下 JavaScript 中变量的作用域问题。在 JS 中变量分为两种:局部变量和全局变量,与此相对应的两种变量的作用域分别为局部作用域和全局作用域。在函数内部定义的变量为局部变量,在函数外部定义的变量称作全局变量。JS 特有的语法规定全局变量在函数内部和外部都可以使用,但是局部变量只能在在函数内部及其子函数中使用,我们来看如下案例:
- var num = 18; //全局变量
- function func() {
- var age = 22; //局部变量
- console.log(age, num); //22 18
- }
- func();
- console.log(num, age); //18 age is not defined
结果分析:num 为全局变量,age 为局部变量,所以在函数内部访问 age 和 num 均可访问到。在函数外部可以访问全局变脸 num,但是访问局部变量 age 时会报错,原因就是局部变量的作用域只在当前函数及其子函数中有效。
针对 JS 变量作用域的特性,我们提出了如下疑问,如何在函数的外部访问局部变量?这时候我们可以考虑使用闭包,闭包实际上就是在函数内部再嵌套一个子函数将子函数作为返回值返回,接下来我们来看如下案例:
- function func1() {
- var age = 22;
- function func2() {
- return age;
- }
- return func2;
- }
- var result = func1(); //得到的是函数func2这一整个函数
- var _num = result(); //得到的是func2的执行结果也就是age的值
- console.log(result); //function func2(){ return age; }
- console.log(_num); //22
结果分析:首先要明白一个函数名称加不加括号的区别,比如说 func1 和 func1(),func1 表示的是这个函数本身,func1() 表示的是这个名叫 func1 的函数的执行结果也就是它的返回值。其次我们来分析闭包的原理,因为 func1 是函数 func2 的子函数,所以在 func2 中可以访问到变量 age,并将这个值作为函数 func2 的返回值返回,最后将整个 func2 函数作为 func1 函数的返回值。最后我们来分析一下 result 和_num 的值,result 是函数 func1 的执行结果,也就是 func1 的返回值 func2 函数本身,_num 的值为 func2 的执行结果也就是变量 age,至此,我们实际上已经将局部变量 age 的值获取到了并存在变量_num 中。我们所提出的如何在函数外部访问局部变量的值的问题就已经解决了。
上一节代码中的 func2 函数实际上就是闭包。
闭包是指有权访问另一个函数作用域变量的函数,创建闭包的通常方式,是在一个函数内部创建另一个函数。
闭包的本质是一个函数,将函数内部和外部连接起来的桥梁。
闭包最大的用处有两个,一是可以在函数外部访问局部变量的值,第二个是变量的持久化,即让变量始终保存在内存中。我们来看如下实现变量累加功能的案例:
- function func1() {
- var age = 18;
- return++age;
- }
- console.log(func1()); // 19
- console.log(func1()); // 19
- console.log(func1()); // 19
结果分析:我们想要实现变量累加的功能,但发现三次打印的结果都是 19,原因是每次调用函数的时候,变量都会被初始化一次,调用完成之后变量会被释放,不会占据内存。应用我们刚才所学的闭包的知识来解决如下问题,代码如下:
- function func1() {
- var age = 18;
- function func2() {
- return++age;
- }
- return func2;
- }
- var result = func1();
- console.log(result()); // 19
- console.log(result()); // 20
- console.log(result()); // 21
结果分析:在这段代码中,result 实际上就是闭包函数,一共运行了三次,三次结果分别为 19、20、21,这也就证明了函数 func1 中的局部变量 age 一直保存在内存中,并没有在 func1 调用后被自动清除。
为什么会这样呢?原因就在于 func1 是 func2 的父函数,而 func2 被赋给了一个全局变量 result,这导致 func2 始终在内存中,而 func2 的存在依赖于 func1,因此 func1 也始终在内存中,不会在调用结束后,被垃圾回收机制回收。
案例二:
为了更好的理解闭包,我们来看另一个例子,下面有一组 li,点击 li 弹出对应的下标:
我们尝试采用如下代码去实现:
- var aLi = document.querySelectorAll("ul li");
- for(var i = 0; i < aLi.length; i++){
- aLi[i].onclick = function(){
- alert(i); // 5
- }
- }
- var aLi = document.querySelectorAll("ul li");
- for(var i = 0; i < aLi.length; i++){
- aLi[i].index = i;
- aLi[i].onclick = function(){
- alert(this.index);
- }
- }
这是我们常用的一种方式,给每个 li 添加一个属性 index ,将 i 的值存储在该属性中,然后去获取触发事件的当前对象的 index 属性值 。学完闭包之后我们尝试用闭包的知识去解决一下如上的问题:
- var aLi = document.querySelectorAll("ul li");
- for(var i = 0; i < aLi.length; i++){
- aLi[i].onclick = (function(index){
- return function(){
- alert(index);
- }
- })(i)
- }
以上代码涉及到匿名函数的自执行以及传值问题,此处不多加阐述。将点击事件后绑定的函数作为匿名函数,将 i 的值作为实参,index 的值则为形参,利用如上解决方案,完美的实现了我们想要的效果。
结语:本文详细阐述了闭包的原理及应用,但是由于闭包的变量持久化的特性会导致函数中的变量也就是局部变量始终都被被存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题。
祝:学习进步,工作顺利!
来源: http://www.cnblogs.com/gaohuijiao/p/6491781.html