闭包的代码示例
注意事项
总结
闭包在javascript来说是比较重要的概念,平时工作中也是用的比较多的一项技术。下来对其进行一个小小的总结
什么是闭包?
官方说法:
闭包是指有权访问另一个函数作用域中的变量的函数。创建闭包的常见方式,就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量------《javascript高级程序设计第三版》
下面就是一个简单的闭包:
function A(){ var text="hello world"; function B(){ console.log(text); } return B; } var c=A(); c(); // hello world
按照字面量的意思是:函数B有权访问函数A作用域中的变量(text),通过另一个函数C来访问这个函数的局部变量text。因此函数B形成了一个闭包。也可以说C是一个闭包,因为C执行的实际是函数B。
这个需要注意的是,直接执行A();是没有任何反应的。因为return B没有执行,除非是return B();
闭包的特性
闭包有三个特性:
1.函数嵌套函数
2.函数内部可以引用外部的参数和变量
3.参数和变量不会被垃圾回收机制回收
解释一下第3点,为什么闭包的参数和变量不会被垃圾回收机制回收呢?
首先我们先了解一下javascript的垃圾回收原理:
(1)、在javascript中,如果一个对象不再被引用,那么这个对象就会被GC(garbage collection)回收;
(2)、如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。
上面的示例代码中A是B的父函数,而B被赋给了一个全局变量C(全局变量的生命周期直至浏览器卸载页面才会结束),这导致B始终在内存中,而B的存在依赖于A,因此A也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
闭包的作用:
其实闭包的作用也是有闭包的特性决定的,根据上面的闭包特性,闭包的作用如下:
1、可以读取函数内部的变量,而不是定义一起全局变量,避免污染环境
2、让这些变量的值始终保持在内存中。
闭包的代码示例
下面主要介绍几种常见的闭包,并进行解析:
demo1 局部变量的累加。
function countFn(){ var count=1; return function(){ //函数嵌套函数 count++; console.log(count); } } var y = countFn(); //外部函数赋给变量y; y(); //2 //y函数调用一次,结果为2,相当于countFn()() y(); //3 //y函数调用第二次,结果为3,因为上一次调用的count还保存在内存中,没有被销毁,所以实现了累加 y=null; //垃圾回收,释放内存 y(); // y is not a function
由于第一次执行完,变量count还保存在内存中,所以不会被回收,以致于第二次执行的时候可以对上次的值就行累加。当引入y=null时,销毁引用,释放内存
demo2 循环中使用闭包
代码如下(下面的三个代码示例):我们的目的是想在每次循环中调用循环序号:
demo2-1
for (var i = 0; i < 10; i++) { var a = function(){ console.log(i) } a() //依次为0--9 }
这个例子的结果是没有题的,我们依次打印出了0-9
每一层匿名函数和变量i都组成了一个闭包,但是这样在循环中并没有问题,因为函数在循环体中立即被执行了
demo2-2
但是在setTimeout中就不一样了
for(var i = 0; i < 10; i++) { setTimeout(function() { console.log(i); //10次10 }, 1000); }
我们期望的依次是打印出0--10,实际情况是打印出 10次10。即使吧setTimeout的时间改为0,也是打印出10个10。这是为什么呢?
这是因为setTimeout的一种机制,setTimeout是从任务队列结束的时候开始计时的,如果前面有进程没有结束,那么它就等到它结束再开始计时。在这里,任务队列就是它自己所在的循环。
循环结束setTimeout才开始计时,所以无论如何,setTimeout里面的i都是最后一次循环的 i。该代码中,最后的 i 为10,所以打印出了10个10.
这也就是为什么setTimeout的回调不是每次取循环时的值,而取最后一次的值
demo2-3
解决上面的setTimeout不能依次打印出循环的问题
for(var i=0;i<10;i++){ var a=function(e){ return function(){ console.log(e); //依次输入0--9 } } setTimeout(a(i),0); }
因为setTimeout第一个参数需要一个函数,所以返回一个函数给它,返回的同时把 i 作为参数传进去,通过形参 e 缓存了i,也就是说e变量相当于是 i 的一个拷贝 ,并带进返回的函数里面。
当 setTimeout 的执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。
也可以用下面的写法,和上面类似:
for(var i = 0; i < 10; i++) { (function(e) { setTimeout(function() { console.log(e); //依次打印出0-9 }, 0); })(i); }
demo3 循环中添加事件
看下面的一个典型的demo.
我们希望每次点击li的时候,alert出li的索引值,所以用下面的代码: