1.什么是闭包???javascript
"官方"的解释是指一个拥有许多变量和绑定了这些变量的环境的表达式(一般是一个函数),于是这些变量也是该表达式的一部分;css
红皮书是这样说的,闭包是指有权访问另外一个函数做用域中变量的函数;常见的建立闭包的方式就是在一个函数中再建立一个函数;html
闭包是一种特殊的对象。它由两部分构成:函数,以及建立该函数的环境。环境由闭包建立时在做用域中的任何局部变量组成;前端
光看定义是云里雾里,可是到了真正的代码了又是什么样的形式呢?经典的闭包例子:java
function fn() { var name = 4; return function () { var n = 0; alert(++n); alert(++name); } } var fun = fn(); fun();//n =>1,name=>5; fun();// n =>1,name=>6;
这里就有闭包产生了,fun就是闭包;这个闭包由fn中的匿名函数和name变量构成。,可是呢,它又是一种特殊的函数,它是一种能可以读取其余函数内部变量的特殊函数;fn中的匿名函数的父函数在执行完了以后,按正常来讲,它应该被销毁,里面的变量也被销毁,,可是里面的变量如今没有被销毁,而是被这个匿名函数引用着;(说实在的它不该该被销毁,由于这个匿名函数尚未执行,还要用上一级的函数的中的变量,你给我销毁了,我可怎么办,那不是让我报错呀,可是呢,我给你用,不销毁,这又不符合规矩,按规矩是这样的:当一个函数执行完以后,是要被当即销毁的,执行环境销毁,里面的变量销毁,你如今不让我销毁,那不乱套了,那怎么办呢,因而乎,一群砖家,就说赶这种方式叫闭包吧)意思是说我跟大家不同;由于是特殊函数,代码的最后一句fun()执行完以后,name变量仍是没有释放,可是每次执行fun,里面的n变量是都是新建立的,执行完以后又释放掉;要是你明白了就不须要看括号里的内容了(这里我就形象的说为何说name变量会一直在内存中?在刚开始的时候,父函数fn在刚要执行完了,开始销毁时,匿名子函数就说了,我要用你的name变量,你先别销毁了,父函数说好吧,因而乎,父函数执行完以后(归天了),就没有销毁name,在当你调用fun,执行匿名子函数,fun()调用完了,你把本身家的n变量销毁了,fun就说了name又不是个人东西,我就是用了一下,凭什么我给销毁,我不给销毁,可是这时父函数已经去世了(执行完了),因而就产生了内存消耗,除非你手动销毁,垃圾收回机制不会自动收回;这又牵扯到内存泄漏,性能问题了,后面说。)闭包
做用域链:ide
讲到这里,若是要想整整的明白还有知道做用域链,和垃圾收回机制;函数
我就说说上面代码执行时的做用域链:性能
我就说说这张图是什么意思,这种图是执行var fun = fu();fun();这两句代码时所发生的状况;ui
其实匿名函数在fu()被返回时,它的做用域链就被初始化为包含全局变量对象和fu函数的活动对象;也就是说当fu函数执行完返回后,它的执行环境会被销毁,可是其活动对象不会被销毁,仍然在内存中,由于匿名函数的做用域链中引用了这个活动对象。只有到匿名函数被手动销毁时才销毁;其实在fu执行完后,红字显示的部分就消失了,就活动变量没有消失;
再说一点关于做用域链的问题:
1。做用域链中的变量对象(函数中叫活动对象)保存的是变量和函数;
2.做用域链的做用就是为了保证对执行环境有权访问的全部变量和函数的有序访问。
3.查找一个变量是从做用域链的最前端,逐渐向上找,只要找到就再也不向上找了,不论上面是否还有这个值;
4.就以上面的fu函数为例吧,在声明fu函数的时候就开始预先建立一个包含全局变量对象的做用域链了(若是嵌套多了,其实就是在函数声明的地方建立父函数及其之上的做用域链),这个做用域链将被保存在刚建立函数的内部[[Scope]]的属性中;当调用fu函数时,会为函数建立一个执行环境,而后经过复制函数的[[Scope]]中的做用域链构建起执行环境的做用域链;以后还要建立一个本函数的活动对象,并把这个活动对象推入执行环境做用域链的前端。
5.做用域链本质上就是一个指向变量对象的指针列表。
垃圾回收机制:
再说说垃圾回收机制:
1.若是一个对象再也不有引用了,这个对象就会被GC收回;
2,若是两个对象互相引用,但不被第三个引用,这两个互相引用的对象也会收回的。
而闭包再也不这个范畴以内。
闭包的特性:
1.引用的变量不被垃圾回收机制收回
2.函数内部能够引用外部的变量;
3.函数里面嵌套函数
闭包的用处(好处):
1.私有变量和方法
var a=(function() { var privateNum = 1; function privateFun(val) { alert(val); } return { publicFun: function() { privateFun(2); }, publicNum:function() { return privateNum; } } })(); a.publicNum();//1 a.publicFun();//2
若是你用a.privateNum,a.privateFun();这是会报错的。
2.实现一些变量的累加
function a() { var n=0; return function () { n++; alert(n); } } var b = a(); b();//1 b();//2 b();//3
这里只是要使用累加,就这样干,具体还要具体分析,原理是这样了
因闭包产生的问题
初学者常见的,循环闭包
大部分咱们所写的 Web JavaScript 代码都是事件驱动的 — 定义某种行为,而后将其添加到用户触发的事件之上(好比点击或者按键)。咱们的代码一般添加为回调:响应事件而执行的函数。
<!DOCTYPE HTML> <html> <head> <meta charset="utf-8"/> <title>闭包循环问题</title> <style type="text/css"> p {background:red;} </style> </head> <body> <p class="p">我是1号</p> <p class="p">我是2号</p> <p class="p">我是3号</p> <p class="p">我是4号</p> <p class="p">我是五号</p> <script type="text/javascript"> var page = document.getElementsByTagName("p"); for(var i=0; i<page.length; i++) { page[i].onclick = function () { alert("我是"+i+"号"); } } </script> </body> </html>
你无论点击哪个,都alert”我是5号“;
缘由就是你循环了五次产生了五个闭包,而这5个闭包共享一个变量i,说的明白一点就是,在for循环结束时,只是把这五个匿名函数注册给click事件,当时在循环的时候并无执行,当循环结束了,此时i的值是5;以后你去点击p标签,你点击哪个就执行哪个对应的匿名函数(这个时候才执行),这时候匿名中发现一个i,匿名中没有定义i,因而沿着做用域链找,找到了,可是这时候循环早就结束了,i等于5,因而弹出”我是5号“来;点击其余的同理;
怎么解决呢:
一种方法是再建立一个闭包,把js代码改成这样就好了
var page = document.getElementsByTagName("p"); for(var i=0; i<page.length; i++) { !function(num) { page[i].onclick = function () { alert("我是"+num+"号"); } }(i) }
我只说一点,此次五个闭包不共享num,而是建立五个num变量
还有一种解决方式:
var page = document.getElementsByTagName("p"); for(var i=0; i<page.length; i++) { page[i].num = i//先把每一个变量值存起来 page[i].onclick = function () { alert("我是"+this.num+"号"); } }
闭包中的this对象
var num = 1; var obj = { num:2, getNum:function() { return function () { return this.num; } } } alert(obj.getNum()());//num -> 1
为何不弹出2呢,这里是说明闭包中你须要注意如今的this的指向那一个对象,其实记住一句话就永远不会用错this的指向问题,this永远指向调用它的做用域;
若是这样写你就可能理解了
var num = 1; var obj = { num:2, getNum:function() { return function () { return this.num; } } } var a = obj.getNum(); alert(window.a());//1
实际上是window对象调用的,这就是说闭包中的this让你看不清this的指向;
要是让它alert 2你要这样:
var num = 1; var obj = { num:2, getNum:function() { var _this = this;//在这里保存this return function () { return _this.num; } } } var a = obj.getNum(); alert(window.a());
性能考量
若是不是由于某些特殊任务而须要闭包,在没有必要的状况下,在其它函数中建立函数是不明智的,由于闭包对脚本性能具备负面影响,包括处理速度和内存消耗。
例如,在建立新的对象或者类时,方法一般应该关联于对象的原型,而不是定义到对象的构造器中。缘由是这将致使每次构造器被调用,方法都会被从新赋值一次(也就是说,为每个对象的建立)。
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); this.getName = function() { return this.name; }; this.getMessage = function() { return this.message; }; }
应该是这样
function MyObject(name, message) { this.name = name.toString(); this.message = message.toString(); } MyObject.prototype.getName = function() { return this.name; }; MyObject.prototype.getMessage = function() { return this.message; };
示例中,继承的原型能够为全部对象共享,且没必要在每一次建立对象时定义方法
欢迎评论指正;
参考:
红皮书
Mozilla:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Closures
阮一峰:http://www.ruanyifeng.com/blog/2009/08/learning_javascript_closures.html