用最通俗易懂的代码帮助新手理解javascript闭包 推荐


Posted in Javascript onMarch 01, 2012

最近看了几篇有关javascript闭包的文章,包括最近正火的汤姆大叔系列,还有《javascript高级程序设计》中的文章,……我看不懂,里面有些代码是在大学教科书中看都没看过的,天书一般。幸好最近遇到两本好书《ppk on javascript》和《object-oriented JavaScript》,正字阅读中,后者还没有中文版,但前者还是建议看原版,写的不复杂,有兴趣的朋友可以看看,适合想进阶的朋友。
今天就结合这两本书,用最浅显的语言和最通俗的方式谈谈javascript中的闭包,因为也是新手,所以有有误的地方请各位指出,谢谢
一. 准备知识
1.函数作为函数的参数
在学习javascript中,你始终要有一个有学习与其他语言不同的概念:函数(function)不么特殊的东西,它也是一种数据,与bool ,string,number没有什么两样。
函数的参数可以string,number,bool如:
function(a, b) {return a + b;}
但同样也可以传入函数。对你没有听错,函数的参数是函数!加入你有以下两个函数:

//把三个数翻一倍 
function multiplyByTwo(a, b, c) { 
var i, ar = []; 
for(i = 0; i < 3; i++) { 
ar[i] = arguments[i] * 2; 
} 
return ar; 
}

//把数加一 
function addOne(a) { 
return a + 1; 
}

然后这么使用
var myarr = []; 
//先把每个数乘以二,用了一个循环 
myarr = multiplyByTwo(10, 20, 30); 
//再把每个数加一,又用了一个循环 
for (var i = 0; i < 3; i++) {myarr[i] = addOne(myarr[i]);}

要注意到其实这个过程用了两个循环,还是有提升的空间的,不如这么做:
function multiplyByTwo(a, b, c, addOne) { 
var i, ar = []; 
for(i = 0; i < 3; i++) { 
ar[i] = addOne (arguments[i] * 2); 
} 
return ar; 
}

这样就把函数当做参数传递进去了,并且在第一个循环中直接调用。这样的函数就是著名的回调函数(Callback function)
2.函数作为返回值
在函数中可以有返回值,但是我们一般都熟悉数值的返回,如
function ex(){ 
return 12 
}

但你一旦意识到函数只是一种数据的话,你就可以想到同样可以返回函数。注意看下面这个函数:
function a() { 
alert('A!'); 
return function(){ 
alert('B!'); 
}; 
}

它返回了一个弹出”B!”的函数。接下来使用它:
var newFunc = a(); 
newFunc();

结果是什么呢?首先执行a()的时候,弹出”A!”,此时newFunc接受了a的返回值,一个函数——此时newFunc就变成了那个被a返回的函数,再执行newFunc时,弹出”B!”
3.javascript的作用域
javascript的作用域很特别,它是以函数为单位的,而不是像其他语言以块为单位(如一个循环中),看下面这个例子:
var a = 1; function f(){var b = 1; return a;}
如果你此时试图想得到b的值:在firebug中试图输入alert(b)的话,你会得到错误提示:
b is not defined
为什么你可以这么理解:你所在的编程环境或者窗口是最顶级的一个函数,好像一个宇宙,但是b只是在你内部函数的一个变量,宇宙中的小星球上的一个点,你很难找到它,所以在这个环境中你不能调用它的;反之这个内部函数可以调用变量a,因为它暴露在整个宇宙中,无处藏身,同时也可以调用b,因为它就在自己的星球上,函数内部。
就上面这个例子说:
在f()外,a可见,b不可见
在f()内,a可见,b也可见
再复杂点:
var a = 1; //b,c在这一层都不可见 
function f(){ 
var b = 1; 
function n() { //a,b,c对这个n函数都可以调用,因为a,b暴露在外,c又是自己内部的 
var c = 3; 
} 
}

问你,函数b可以调用变量c吗?不行,记住javascript的作用域是以函数为单位的,c在n的内部,所以对f来说是不可见的。

开始正式谈闭包:

首先看这个图:

 

用最通俗易懂的代码帮助新手理解javascript闭包 推荐

假设G,F,N 分别代表三个层次的函数,层次如图所示,a,b,c分别是其中的变量。根据上面谈到的作用域,我们有如下结论:

  1. 如果你在a点,你是不可以引用b的,因为b对你是不可见的
  2. 只有c可以引用b

闭包的吊诡之处的就在于发生了如下情况:

 

用最通俗易懂的代码帮助新手理解javascript闭包 推荐

N突破了F的限制!跑到于a同一层了!因为函数只认它们在定义时所处的环境而不是执行时,这点很重要),N中的c仍然可以访问b!此时的a还是不可以访问b!

但是这是怎么实现的呢?如下:
闭包1:

function f(){ 
var b = "b"; 
return function(){ //没有名字的函数,所以是匿名函数 
return b; 
} 
}

注意返回的函数可以访问它父亲函数中的变量b
此时如果你想取b的值,当然是undefined
但是如果你这么做:
var n = f(); 
n();

你可以取到b的值了!虽然此时n函数在f的外面,b又属于f内部的变量,但是f内部出了一个内鬼,返回了b的值……
现在大家有点感觉了吧
闭包2:
var n; 
function f(){ 
var b = "b"; 
n = function(){ 
return b; 
} 
}

如果此时调用f会怎么样?那就生成了一个n的全局范围函数,但是它却能访问f的内部,照样返回b的值,与上面有异曲同工之妙!
闭包3:
你还可以用闭包访问函数的参数
function f(arg) { 
var n = function(){ 
return arg; 
}; 
arg++; 
return n; 
}

此时如果使用:
var m = f(123); 
m();

结果是124
因为此时f中返回的匿名函数经过了两道转手,先给n,再赋给外面的m,但本质没有变,把定义时父函数的参数返回了
闭包4:
var getValue, setValue; 
function() { 
var secret = 0; 
getValue = function(){ 
return secret; 
}; 
setValue = function(v){ 
secret = v; 
}; 
})

运行:
getValue() 
0 
setValue(123) 
getValue() 
123

这个就不用解释了吧,如果你有面向对象语言基础的话(如C#),这里的getValue和setValue就类似于一个对象的属性访问器,你可以通过这两个访问器来赋值和取值,而不是能访问其中内容
其实书中还有几个闭包的例子,但是原理用上面四个就足够了,希望能起抛砖引玉的作用,给javascript进阶者对闭包有一个更深刻的理解
Javascript 相关文章推荐
JavaScript 密码强度判断代码
Sep 05 Javascript
用JQUERY增删元素的代码
Feb 14 Javascript
如何在父窗口中得知window.open()出的子窗口关闭事件
Oct 15 Javascript
基于jQuery实现的双11天猫拆红包抽奖效果
Dec 01 Javascript
jquery插件treegrid树状表格的使用方法详解(.Net平台)
Jan 03 Javascript
jQuery查找和过滤_动力节点节点Java学院整理
Jul 04 jQuery
zTree树形菜单交互选项卡效果的实现方法
Dec 25 Javascript
vue实例中data使用return包裹的方法
Aug 27 Javascript
浅谈微信页面入口文件被缓存解决方案
Sep 29 Javascript
微信小程序显示倒计时功能示例【测试可用】
Dec 03 Javascript
vue父子组件间引用之$parent、$children
May 20 Javascript
浅谈vue中$bus的使用和涉及到的问题
Jul 28 Javascript
关于js日期转化为毫秒数“节省20%的效率和和节省9个字符“问题
Mar 01 #Javascript
JavaScript中使用构造器创建对象无需new的情况说明
Mar 01 #Javascript
JavaScript中的变量声明早于赋值分析
Mar 01 #Javascript
JavaScript中函数声明优先于变量声明的实例分析
Mar 01 #Javascript
Jquery提交表单 Form.js官方插件介绍
Mar 01 #Javascript
jquery.artwl.thickbox.js  一个非常简单好用的jQuery弹出层插件
Mar 01 #Javascript
jQuery AJAX实现调用页面后台方法和web服务定义的方法分享
Mar 01 #Javascript
You might like
用PHP和MySQL保存和输出图片
2006/10/09 PHP
PHP文件上传处理案例分析
2016/10/15 PHP
对于Laravel 5.5核心架构的深入理解
2018/02/22 PHP
PHP实现的mongoDB数据库操作类完整实例
2018/04/10 PHP
JavaScript 变量作用域分析
2011/07/04 Javascript
实用的JS正则表达式(手机号码/IP正则/邮编正则/电话等)
2013/01/11 Javascript
使用js检测浏览器的实现代码
2013/05/14 Javascript
多种方法实现JS动态添加事件
2013/11/01 Javascript
JS文本获得焦点清除文本文字的示例代码
2014/01/13 Javascript
jQuery学习笔记之jQuery.extend(),jQuery.fn.extend()分析
2014/06/09 Javascript
js实现键盘操作实现div的移动或改变的原理及代码
2014/06/23 Javascript
JavaScript 浏览器对象模型BOM使用介绍
2015/04/13 Javascript
JS创建对象几种不同方法详解
2016/03/01 Javascript
jQuery 3 中的新增功能汇总介绍
2016/06/12 Javascript
vue-router实现webApp切换页面动画效果代码
2017/05/25 Javascript
教你如何用node连接redis的示例代码
2018/07/12 Javascript
微信小程序动态生成二维码的实现代码
2018/07/25 Javascript
对layui中的onevent 和event的使用详解
2019/09/06 Javascript
[01:53]2016完美“圣”典风云人物:Maybe专访
2016/12/05 DOTA
Python中对元组和列表按条件进行排序的方法示例
2015/11/10 Python
Python常见格式化字符串方法小结【百分号与format方法】
2016/09/18 Python
Python爬虫实例_利用百度地图API批量获取城市所有的POI点
2018/01/10 Python
python微信跳一跳系列之棋子定位颜色识别
2018/02/26 Python
Django中使用Celery的教程详解
2018/08/24 Python
Selenium定时刷新网页的实现代码
2018/10/31 Python
我就是这样学习Python中的列表
2019/06/02 Python
TensorFlow自定义损失函数来预测商品销售量
2020/02/05 Python
Python写出新冠状病毒确诊人数地图的方法
2020/02/12 Python
Python爬虫程序架构和运行流程原理解析
2020/03/09 Python
Python语言编写智力问答小游戏功能
2020/10/13 Python
HTML5之SVG 2D入门5—颜色的表示及定义方式
2013/01/30 HTML / CSS
什么是Rollback Segment
2013/04/22 面试题
理财计划书
2014/08/14 职场文书
部队2014年终工作总结
2014/11/27 职场文书
小学家长意见怎么写
2015/06/03 职场文书
Oracle删除归档日志及添加定时任务
2022/06/28 Oracle