- 浏览: 454413 次
- 性别:
- 来自: 西安
文章分类
最新评论
-
进退取舍:
谢谢,这个用上了!!
Java 一个线程池的示例 -
pb_water:
感谢楼主,打算买楼主的书,支持一下,楼主功德无量
JavaScript内核系列第0版整理稿下载 -
lancezhcj:
有图会直观的多呢,再摸索摸索
有限自动机与建模 -
hsmsyy:
这里应该是原创了吧,楼主我觉得闭包的作用:实现面向对象。有待商 ...
JavaScript内核系列 第7章 闭包 -
wll52:
在应用退出之前,需要释放连接 con.disconnect() ...
使用smack与GTalk通信
第七章 闭包
闭包向来给包括JavaScript程序员在内的程序员以神秘,高深的感觉,事实上,闭包的概念在函数式编程语言中算不上是难以理解的知识。如果对作用域,函数为独立的对象这样的基本概念理解较好的话,理解闭包的概念并在实际的编程实践中应用则颇有水到渠成之感。
在DOM的事件处理方面,大多数程序员甚至自己已经在使用闭包了而不自知,在这种情况下,对于浏览器中内嵌的JavaScript引擎的bug可能造成内存泄漏这一问题姑且不论,就是程序员自己调试也常常会一头雾水。
用简单的语句来描述JavaScript中的闭包的概念:由于JavaScript中,函数是对象,对象是属性的集合,而属性的值又可以是对象,则在函数内定义函数成为理所当然,如果在函数func内部声明函数inner,然后在函数外部调用inner,这个过程即产生了一个闭包。
7.1闭包的特性
我们先来看一个例子,如果不了解JavaScript的特性,很难找到原因:
var outter = [];
function clouseTest () {
var array = ["one", "two", "three", "four"];
for(var i = 0; i < array.length;i++){
var x = {};
x.no = i;
x.text = array[i];
x.invoke = function(){
print(i);
}
outter.push(x);
}
}
//调用这个函数
clouseTest();
print(outter[0].invoke());
print(outter[1].invoke());
print(outter[2].invoke());
print(outter[3].invoke());
运行的结果如何呢?很多初学者可能会得出这样的答案:
1
2
3
然而,运行这个程序,得到的结果为:
4
4
4
其实,在每次迭代的时候,这样的语句x.invoke = function(){print(i);}并没有被执行,只是构建了一个函数体为”print(i);”的函数对象,如此而已。而当i=4时,迭代停止,外部函数返回,当再去调用outter[0].invoke()时,i的值依旧为4,因此outter数组中的每一个元素的invoke都返回i的值:4。
如何解决这一问题呢?我们可以声明一个匿名函数,并立即执行它:
var outter = [];
function clouseTest2(){
var array = ["one", "two", "three", "four"];
for(var i = 0; i < array.length;i++){
var x = {};
x.no = i;
x.text = array[i];
x.invoke = function(no){
return function(){
print(no);
}
}(i);
outter.push(x);
}
}
clouseTest2();
这个例子中,我们为x.invoke赋值的时候,先运行一个可以返回一个函数的函数,然后立即执行之,这样,x.invoke的每一次迭代器时相当与执行这样的语句:
//x == 0 x.invoke = function(){print(0);} //x == 1 x.invoke = function(){print(1);} //x == 2 x.invoke = function(){print(2);} //x == 3 x.invoke = function(){print(3);}
这样就可以得到正确结果了。闭包允许你引用存在于外部函数中的变量。然而,它并不是使用该变量创建时的值,相反,它使用外部函数中该变量最后的值。
7.2闭包的用途
现在,闭包的概念已经清晰了,我们来看看闭包的用途。事实上,通过使用闭包,我们可以做很多事情。比如模拟面向对象的代码风格;更优雅,更简洁的表达出代码;在某些方面提升代码的执行效率。
7.2.1 匿名自执行函数
上一节中的例子,事实上就是闭包的一种用途,根据前面讲到的内容可知,所有的变量,如果不加上var关键字,则默认的会添加到全局对象的属性上去,这样的临时变量加入全局对象有很多坏处,比如:别的函数可能误用这些变量;造成全局对象过于庞大,影响访问速度(因为变量的取值是需要从原型链上遍历的)。除了每次使用变量都是用var关键字外,我们在实际情况下经常遇到这样一种情况,即有的函数只需要执行一次,其内部变量无需维护,比如UI的初始化,那么我们可以使用闭包:
var datamodel = {
table : [],
tree : {}
};
(function(dm){
for(var i = 0; i < dm.table.rows; i++){
var row = dm.table.rows[i];
for(var j = 0; j < row.cells; i++){
drawCell(i, j);
}
}
//build dm.tree
})(datamodel);
我们创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。
7.2.2缓存
再来看一个例子,设想我们有一个处理过程很耗时的函数对象,每次调用都会花费很长时间,那么我们就需要将计算出来的值存储起来,当调用这个函数的时候,首先在缓存中查找,如果找不到,则进行计算,然后更新缓存并返回值,如果找到了,直接返回查找到的值即可。闭包正是可以做到这一点,因为它不会释放外部的引用,从而函数内部的值可以得以保留。
var CachedSearchBox = (function(){
var cache = {},
count = [];
return {
attachSearchBox : function(dsid){
if(dsid in cache){//如果结果在缓存中
return cache[dsid];//直接返回缓存中的对象
}
var fsb = new uikit.webctrl.SearchBox(dsid);//新建
cache[dsid] = fsb;//更新缓存
if(count.length > 100){//保正缓存的大小<=100
delete cache[count.shift()];
}
return fsb;
},
clearSearchBox : function(dsid){
if(dsid in cache){
cache[dsid].clearSelection();
}
}
};
})();
CachedSearchBox.attachSearchBox("input1");
这样,当我们第二次调用CachedSearchBox.attachSerachBox(“input1”)的时候,我们就可以从缓存中取道该对象,而不用再去创建一个新的searchbox对象。
7.2.3 实现封装
可以先来看一个关于封装的例子,在person之外的地方无法访问其内部的变量,而通过提供闭包的形式来访问:
var person = function(){
//变量作用域为函数内部,外部无法访问
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
}();
print(person.name);//直接访问,结果为undefined
print(person.getName());
person.setName("abruzzi");
print(person.getName());
得到结果如下:
default
abruzzi
闭包的另一个重要用途是实现面向对象中的对象,传统的对象语言都提供类的模板机制,这样不同的对象(类的实例)拥有独立的成员及状态,互不干涉。虽然JavaScript中没有类这样的机制,但是通过使用闭包,我们可以模拟出这样的机制。还是以上边的例子来讲:
function Person(){
var name = "default";
return {
getName : function(){
return name;
},
setName : function(newName){
name = newName;
}
}
};
var john = Person();
print(john.getName());
john.setName("john");
print(john.getName());
var jack = Person();
print(jack.getName());
jack.setName("jack");
print(jack.getName());
运行结果如下:
john
default
jack
由此代码可知,john和jack都可以称为是Person这个类的实例,因为这两个实例对name这个成员的访问是独立的,互不影响的。
事实上,在函数式的程序设计中,会大量的用到闭包,我们将在第八章讨论函数式编程,在那里我们会再次探讨闭包的作用。
7.3应该注意的问题
7.3.1内存泄漏
在不同的JavaScript解释器实现中,由于解释器本身的缺陷,使用闭包可能造成内存泄漏,内存泄漏是比较严重的问题,会严重影响浏览器的响应速度,降低用户体验,甚至会造成浏览器无响应等现象。
JavaScript的解释器都具备垃圾回收机制,一般采用的是引用计数的形式,如果一个对象的引用计数为零,则垃圾回收机制会将其回收,这个过程是自动的。但是,有了闭包的概念之后,这个过程就变得复杂起来了,在闭包中,因为局部的变量可能在将来的某些时刻需要被使用,因此垃圾回收机制不会处理这些被外部引用到的局部变量,而如果出现循环引用,即对象A引用B,B引用C,而C又引用到A,这样的情况使得垃圾回收机制得出其引用计数不为零的结论,从而造成内存泄漏。
7.3.2上下文的引用
关于this我们之前已经做过讨论,它表示对调用对象的引用,而在闭包中,最容易出现错误的地方是误用了this。在前端JavaScript开发中,一个常见的错误是错将this类比为其他的外部局部变量:
$(function(){ var con = $("div#panel"); this.id = "content"; con.click(function(){ alert(this.id);//panel }); });
此处的alert(this.id)到底引用着什么值呢?很多开发者可能会根据闭包的概念,做出错误的判断:
理由是,this.id显示的被赋值为content,而在click回调中,形成的闭包会引用到this.id,因此返回值为content。然而事实上,这个alert会弹出”panel”,究其原因,就是此处的this,虽然闭包可以引用局部变量,但是涉及到this的时候,情况就有些微妙了,因为调用对象的存在,使得当闭包被调用时(当这个panel的click事件发生时),此处的this引用的是con这个jQuery对象。而匿名函数中的this.id = “content”是对匿名函数本身做的操作。两个this引用的并非同一个对象。
如果想要在事件处理函数中访问这个值,我们必须做一些改变:
$(function(){ var con = $("div#panel"); this.id = "content"; var self = this; con.click(function(){ alert(self.id);//content }); });
这样,我们在事件处理函数中保存的是外部的一个局部变量self的引用,而并非this。这种技巧在实际应用中多有应用,我们在后边的章节里进行详细讨论。关于闭包的更多内容,我们将在第九章详细讨论,包括讨论其他命令式语言中的“闭包”,闭包在实际项目中的应用等等。
附:由于作者本身水平有限,文中难免有纰漏错误等,或者语言本身有不妥当之处,欢迎及时指正,提出建议,参与讨论,谢谢大家!
评论
function Person(){
var name = "default";
var getName = function(){
return name;
};
var setName = function(newName){
name = newName;
}
};
var john = new Person();
print(john.getName());
john.setName("john");
print(john.getName());
var jack = new Person();
print(jack.getName());
jack.setName("jack");
print(jack.getName());
这样写和闭包的写法是一样的吧,所以用闭包应该没有意义
<pre name="code" class="缓存器">var adder = function(n){
var t = 10;
var org = n+"-"+Math.random();
return{
getinfo:function(){
document.writeln("\n\t"+org);
return n+t;
}
};
};
var CachedSearchBox = (function(){
var cache = {},count = [],totalCount = 20;
return {
attachSearchBox:function(dsid){
if(dsid in cache){
print("old:::"+cache[dsid].getinfo());
return cache[dsid];
}
var fsb = new adder(dsid);
print("new::::"+fsb.getinfo());
cache[dsid] = fsb;
if(count.length>=totalCount){
var out = count.shift();
print("del::::::::::::"+out.getinfo())
delete cache[out];
}
count.push(fsb);
return fsb;
},
clearSearchBox:function(dsid){
if(dsid in cache){
//cache[dsid].clearSelection();
delete cache[dsid];
count.shift();
}
},
getcount:function(){//获取对象的长度
print("array length:"+count.length);
return count.length;
},
setTotalCount:function(c){//设置缓存大小
totalCount = c;
}
};
})();
CachedSearchBox.getcount();
print("<br>");
(function(){
for(var i=0;i<100;i++){
CachedSearchBox.attachSearchBox(Math.ceil(Math.random()*120))
print("~~~~~");
CachedSearchBox.getcount();
print("<br>");
if(i==30)
CachedSearchBox.setTotalCount(40);
}
})();
function print(a1){
document.writeln(a1+"\n\t");
}</pre>
<p> </p>
一个是变量声明,一个是匿名调用。
嗯,这儿起同样的名字确实让人混淆。
var person = function(){ //变量作用域为函数内部,外部无法访问 var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }(); print(person.name);//直接访问,结果为undefined print(person.getName()); person.setName("abruzzi"); print(person.getName());
var person = function(){ var name = "default"; return { getName : function(){ return name; }, setName : function(newName){ name = newName; } } }; var john = person(); print(john.getName()); john.setName("john"); print(john.getName()); var jack = person(); print(jack.getName()); jack.setName("jack"); print(jack.getName());
请LZ参考。
谢谢你的建议。
刚开始些这章的时候,考虑如果涉及一大堆的理论部分,难免看着很眩晕,毕竟闭包的概念本身并不复杂。加上之前关于执行环境,作用域都已经讲过了。但是只写成关于闭包的特点,用途等又有点单薄,不堪深入思考。我尽量再权衡一下,做些理论方面的补充吧。
请LZ参考。
7.2.1 匿名自执行函数 这一小节为什么会出现在这里?感觉跟闭包关系没有什么关系
不好意思,这个例子确实不应该在这里,因为没有一个全局变量hold这个匿名自执行函数的返回值(因为这个例子中,该函数执行完之后就释放了),想要说的应该是Cache那个例子,呵呵。
谢谢你提出的问题!
7.2.1 匿名自执行函数 这一小节为什么会出现在这里?感觉跟闭包关系没有什么关系
嗯,对照着看挺好。不过我尽量再在其中加入些理论的东西,不论是只有理论,还是只有实践,都太单薄,两者结合才好。
发表评论
-
JavaScript内核系列 第15章 服务器端的JavaScript
2012-02-12 21:39 2385第15章已经在icodeit上发布,这一章分为上/下两篇,请朋 ... -
使用vim开发python及graphviz绘图
2011-12-23 14:49 6494基本需求 使用vim中的autocmd命令可以很容易的将正在 ... -
Java脚本技术应用实例
2011-01-22 11:24 4312前言 一直以来都很喜欢可以自由扩展的软件,这一点应该已经在很 ... -
可编程计算器(phoc)的设计与实现
2011-01-17 11:34 2017前言 借助JavaScript脚本 ... -
函数式编程(javascirpt)
2009-04-18 22:18 1291前言 Javascript,有人称 ... -
C和指针
2009-05-21 23:15 1135前言 指针是C的灵魂,正是指针使得C存在了这么多年,而且将长 ... -
C和指针(续)
2009-05-25 23:41 1386前言 上一篇《C和指针》可能对关于C和指针的有些内容没有说透 ... -
有限自动机与建模
2009-06-06 10:48 1831前言 在学校学程序设计语言的时候,能接触到的所有例子没有一个 ... -
事件和监听器
2009-06-21 22:06 1456前言 事件监听器是经 ... -
基于总线的消息服务(BBMS)的设计与实现
2009-07-25 22:19 1372前言 异步事件的通知机制在比较有规模的软件设计中必然会有涉及 ... -
JavaScript内核系列 第9章 函数式的Javascript
2010-05-13 19:20 3817第九章 函数式的Javascript 要说Ja ... -
JavaScript内核系列 第8章 面向对象的JavaScript(下)
2010-05-06 09:40 3681接上篇:JavaScript内核系列 第8章 面向对象的Jav ... -
JavaScript内核系列 第8章 面向对象的JavaScript(上)
2010-05-06 09:26 2930第八章 面向对象的 Javascript ... -
JavaScript内核系列 第6章 正则表达式
2010-04-27 19:44 4084第六章 正则表达式 正则表达式是对字符串的结构 ... -
JavaScript内核系列 第5章 数组
2010-04-24 15:17 4601第五章 数组 JavaScript的数组也是一个比较 ... -
Swing小应用(Todo-List)之三
2010-04-22 20:47 2149前言 去年9月份开发的那个小工具sTodo,只是做到了能用, ... -
JavaScript内核系列 第4章 函数
2010-04-18 17:31 5113第四章 函数 函数,在C语言之类的过程式语言中 ... -
JavaScript内核系列 第3章 对象与JSON
2010-04-12 09:12 6182第三章 对象与JSON JavaScript对象与传 ... -
JavaScript内核系列 第2章 基本概念
2010-04-03 19:44 5744第二章 基本概念 ... -
JavaScript内核系列 第1章 前言及概述
2010-04-01 23:15 9980前言 从2006年第一次接触JavaScript至今,算来也 ...
相关推荐
第7章 01 ip地址与子网划分 02 ip地址配置 03 虚拟机网络模式 04 三层隔离验证试验 第8章 01 上节课复习 02 软件包介绍 03 rpm软件包管理 04 yum软件包管理 05 源码安装python3.5 06 ssh服务 07 apache服务 08 ...
第7章ActionScript3.0中的日期和时间 139 7.1日期与时间 139 7.1.1创建日期对象 139 7.1.2日期对象的属性与方法 140 7.1.3日期格式化 143 7.2时间间隔 144 7.2.1使用Timer类 144 7.2.2秒表示例 146 7.3小结 149 第8...
前端开发是现代互联网应用的核心组成部分,涉及到一系列的技术和概念。以下是一些重要的前端开发知识点,主要涵盖HTML、CSS、JavaScript、AJAX以及新兴框架如React、Vue和Angular等。 1. HTML篇: - Web标准理解:...
在IT行业中,性能分析是优化应用程序的关键环节,尤其是在JavaScript领域,因为JavaScript是许多网页和Web应用的核心编程语言。本文将详细讲解如何配置应用程序以监控其帧率(FPS)和内存占用,以提升性能。 首先,...
根据给定文件的信息,我们可以提炼出一系列与前端开发相关的知识点,包括但不限于技术概念、编码实践、面试技巧等。下面将围绕这些方面展开详细介绍。 ### 一、箭头函数与普通函数的区别 箭头函数和普通函数的主要...
数据类型、面向对象、继承、闭包、插件、作用域、跨域、原型链、模块化、自定义事件、内存泄漏、事件机制、异步装载回调、模板引擎、Nodejs、JSON、ajax等。 其他: HTTP、安全、正则、优化、重构、响应式、移动端、...
数据类型、面向对象、继承、闭包、插件、作用域、跨域、原型链、模块化、自定义事件、内存泄漏、事件机制、异步装载回调、模板引擎、Nodejs、JSON、ajax等。 其他: HTTP、安全、正则、优化、重构、响应式、移动端、...
- parseInt接受两个参数,第一个是字符串,第二个是基数(radix)。 - 因此,['1','2','3'].map((item, index) => parseInt(item, index))中,parseInt将索引作为基数,导致解析结果不符合预期。 4. 防抖与节流:...