《javascript设计模式与开发实践》阅读笔记(四)
来源:互联网 发布:php递归算法经典实例 编辑:程序博客网 时间:2024/05/21 09:13
闭包问题
变量的作用域
变量的生存周期
对于全局变量来说,生存周期是永久的;对于局部变量而言,一旦退出函数,局部变量就会被销毁。
下面看一段代码,代码示例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里面不存在块级作用域。
解决方案1
做一点小改动,将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>
解决方案2
结合上面的思想,我们只需要把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了。
解决方案3,通过this
闭包的其他应用
1.封装变量
例如一个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))//6console.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指向宿主对象。
2.延续局部变量的作用域
闭包和面向对象设计
先看由前面引申而来的一个包含闭包的函数,这个返回的是一个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); //0extent.call(); //1extent.call(); //2extent.call(); //3console.log(extent.value); //3
一个具有私有属性的单例模式的实现,这里就不能通过extent._value访问到value了,但是通过call可以。
var extent = (function() { var _value = 0; return { call: function() { _value++; console.log(_value); } }}())extent.call() //1extent.call() //2extent.call() //3console.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对象的垃圾收集机制采用的是引用技术策略。
- 《javascript设计模式与开发实践》阅读笔记(四)
- 《javascript设计模式与开发实践》阅读笔记(一)
- 《javascript设计模式与开发实践》阅读笔记(二)
- 《javascript设计模式与开发实践》阅读笔记(三)
- 《javascript设计模式与开发实践》阅读笔记(10)—— 组合模式
- 《javascript设计模式与开发实践》阅读笔记(11)—— 模板方法模式
- 《javascript设计模式与开发实践》阅读笔记(12)—— 享元模式
- 《javascript设计模式与开发实践》阅读笔记(14)—— 中介者模式
- 《javascript设计模式与开发实践》阅读笔记(11)—— 模板方法模式
- 《javascript设计模式与开发实践》阅读笔记(12)—— 享元模式
- 《javascript设计模式与开发实践》阅读笔记(10)—— 组合模式
- 《javascript设计模式与开发实践》阅读笔记(11)—— 模板方法模式
- 《javascript设计模式与开发实践》阅读笔记(12)—— 享元模式
- 《javascript设计模式与开发实践》阅读笔记(10)—— 组合模式
- 《javascript设计模式与开发实践》阅读笔记(11)—— 模板方法模式
- 《javascript设计模式与开发实践》阅读笔记(14)—— 中介者模式
- 《javascript设计模式与开发实践》阅读笔记(五)——高阶函数
- 《javascript设计模式与开发实践》阅读笔记六(高阶函数AOP)
- 测试
- linux系统中rsync+inotify实现服务器之间文件实时同步
- PAT乙级1026-1030
- 分布式速记
- Java面试题1
- 《javascript设计模式与开发实践》阅读笔记(四)
- dp的物理大小
- spring相关类
- 浅谈CDN内容分发网络
- android中调用系统功能 来显示本地相册图片 拍照 视频 音频功能
- sun.misc.Unsafe的使用---使用堆外内存空间存储整形数组
- js处理层级数据结构的一些总结
- Android属性动画运用实例
- 问题解决1@hdfs实际数据和备份数据不一致