对于全局变量来说,生存周期是永久的;对于局部变量而言,一旦退出函数,局部变量就会被销毁。 下面看一段代码,代码示例3_1_2.html
<script type="text/javascript"> var func=function(){ var a=1; return function(){ a++; alert(a); } } var f=func(); f();//2 f();//3 f();//4 </script>很有意思,a并没有被销毁。原因是f返回了一个匿名函数,而这个匿名函数访问了a,局部变量一直在匿名函数的环境里。这里测试了一下,确实f是一个匿名函数。也就是说f没有被销毁,那么a就不会被销毁咯?
先上代码,3_1_3.html
<html> <head> <title></title> </head> <body> <div>1</div> <div>2</div> <div>3</div> <div>4</div> <div>5</div> </body> <script type="text/javascript"> var nodes=document.getElementsByTagName('div'); for (var i = 0 ; i < nodes.length; i++) { nodes[i].onclick=function(){ alert(i); } } </script> </html>无论点击1、2、3、4、5都是显示5。 这是为什么呢?我们读一下代码,我们是为每一个node添加的onclick事件啊?为什么会这么怪异呢?
原因是onclick事件是异步触发的,当事件被触发时,for循环早就结束。 另外,说一下i的作用域是全局作用域,js里面不存在块级作用域。
做一点小改动,将var 变成let,就可以解决。这里理解一下为什么可以解决:因为let是块级作用域,也就是i的生存周期只在for循环中,就不存在alert(i)的i是全局中的i了。
<script type="text/javascript"> var nodes=document.getElementsByTagName('div'); for (let i = 0 ; i < nodes.length; i++) { nodes[i].onclick=function(){ alert(i); } } </script>结合上面的思想,我们只需要把i的作用域包裹在一个局部作用域中,就能解决这个问题。我们采用闭包的方式来看一下呢:
var nodes=document.getElementsByTagName('div'); for (var i = 0 ; i < nodes.length; i++) { (function(i){ nodes[i].onclick=function(){ alert(i); } })(i) }像这样,我们把i放在一个函数里面,那么在作用域链上面就会先查找该函数内部的i,再找到全局的i,这样的i就不是5了。
例如一个mult函数(计算乘积的函数)
var cache = {}; var mult = function() { var args = Array.prototype.join.call(arguments, ','); if (cache[args]) { return cache[args]; } var a = 1; for (var i = 0, l = arguments.length; i < l; i++) { a = a * arguments[i]; } return cache[args] = a; } console.log(cache); //{} console.log(mult(1, 2, 3))//6 console.log(cache);//{ '1,2,3': 6 }因为担心mult一旦作用结束,那么cache就被销毁了,但是通过闭包可以把cache封装在mult函数中,同时随着mult()函数调用结束,cache也不会消失;
var mult = (function() { // body... var cache = {}; return function() { var args = Array.prototype.join.call(arguments, ','); console.log("cache"); console.log(cache); if (args in cache) { return cache[args] } var a = 1; for (var i = 0, l = arguments.length; i < l; i++) { a = a * arguments[i]; } return cache[args] = a; } })() console.log(mult(1, 2, 3)); console.log(mult(1, 2, 3));这样在外部访问不到cache,但是cache却存在着,真的是太神奇了。mult(1, 2, 3)这个函数返回的是一个匿名函数的引用,而并不是一个简单的对象或者是一个基本类型,这个函数可以访问到mult内的cache。所以cache的生命得以延续下来了。很有意思。
再看一下下面这种写法,将计算部分提出去了,使得代码更加优雅。
var mult = (function() { // body... var cache = {}; var calculate = function() { var a = 1; for (var i = 0, l = arguments.length; i < l; i++) { a = a * arguments[i]; } return a; } return function() { var args = Array.prototype.join.call(arguments, ','); console.log("cache"); console.log(cache); if (args in cache) { return cache[args] } return cache[args] = calculate.apply(null, arguments); } })() console.log(mult(1, 2, 3)); console.log(mult(1, 2, 3));calculate.apply(null, arguments);中的null表示this指向宿主对象。
先看由前面引申而来的一个包含闭包的函数,这个返回的是一个call,call是一个函数,却是在extent内部的函数,因此它可以访问value;
var extent = function() { var value = 0; return { call: function() { value++; console.log(value); } } } var e = extent(); e.call() e.call() e.call()单例模式的实现: 其实以前都不知道单例模式运用到了闭包,确实,这就是闭包啊。call可以访问extent内部的变量,value可以被call改变。这是一个简单的单例模式,并没有设置私有变量。
var extent = { value: 0, call: function() { this.value++; console.log(this.value); } } console.log(extent.value); //0 extent.call(); //1 extent.call(); //2 extent.call(); //3 console.log(extent.value); //3一个具有私有属性的单例模式的实现,这里就不能通过extent._value访问到value了,但是通过call可以。
var extent = (function() { var _value = 0; return { call: function() { _value++; console.log(_value); } } }()) extent.call() //1 extent.call() //2 extent.call() //3 console.log(extent._value); //undefined原始的例子:
<body> <button id="execute">点我执行命令</button> <button id="undo">点我执行命令</button> </body> <script type="text/javascript"> var Tv={ open:function(){ console.log('打开电视机'); }, close:function () { console.log('关闭电视机'); } } var OpenTvCommand=function(receiver){ this.receiver=receiver; } OpenTvCommand.prototype.execute=function(){ this.receiver.open(); } OpenTvCommand.prototype.undo=function(){ this.receiver.close(); } var setCommand=function(command){ document.getElementById('execute').onclick=function(){ command.execute(); } document.getElementById('undo').onclick=function(){ command.undo(); } } setCommand(new OpenTvCommand(Tv)) </script> </html>采用闭包后
var Tv = { open: function() { console.log('打开电视机'); }, close: function() { console.log('关闭电视机'); } } var createCommand = function(receiver) { var execute = function() { return receiver.open() } var undo = function() { return receiver.close() } return { execute: execute, undo: undo } }; var setCommand = function(command) { document.getElementById('execute').onclick = function() { command.execute(); } document.getElementById('undo').onclick = function() { command.undo(); } } setCommand(createCommand(Tv));说实话我不知道有什么区别,大概就是createCommand采用了一种单例模式的方法?将execute和undo方法定义在了内部。之前是通过原型的方式实现的,就需要new 创建一个对象。
局部变量本来应该在函数退出的时候就解除引用,但是由于被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去。闭包会存在一些数据无法被及时销毁的,但是如果将来需要回收这些变量,可以手动设置这些变量为null。 跟闭包和内存泄露有关的是会出现循环引用,如果闭包的作用域链中保存着一些DOM节点,那么就有可能造成内存泄露。但是这不是闭包的问题,也不是javascript的问题,而是由于BOM和DOM的对象是使用C++以COM对象的方式实现的,而COM对象的垃圾收集机制采用的是引用技术策略。