浅谈Javascript嵌套函数及闭包


Posted in Javascript onNovember 09, 2010

【嵌套函数】

JavaScript允许嵌入的函数,允许函数用作数据,并且在函数词法作用域下面,可以产生与传统面向对象语言不同的惊人地方。

首先,JavaScript的函数是通过词法来划分作用域的,而不是动态的划分作用域的,于是,函数的是在定义它们的作用域中运行,而不是在执行它们的作用域中运行,所以,当嵌套函数和它的外围函数定义在同一个词法作用域中的时候,是很容易理解的。比如下面很平淡无奇的代码:

var x = 'global'; 
function f () { 
var x = 'local'; 
function g() { 
alert(x); 
} 
g(); 
} 
f(); // 'local'

当f()调用的时候,作用域链可以理解为由两部分组成,包含f这一调用的调用对象,然后后面是全局对象。此时查找x的值,会先从f的调用对象中查找,如果没有,再查找后面全局对象中x。同理,g因为是f的一个嵌套函数,那么,g调用的时候,作用域链应该就是由三部分组成了,g的调用对象,f的调用对象,和全局对象。函数g是要输出x的值,所以会先在g的调用对象中查找x的值,g中没有定义,接下来查找外围f调用对象中x的定义,于是找到了x='local',那么就会输出x,而不会继续往下查找全局对象了。 如果f中也没定义x的值,那么就会继续查找作用域链后面的全局对象,结果就是global了。如果全局对象中也没定义,那么自然就是undefined。

好了,我们对作用域链有了个初步的理解,同时我们知道,闭包有两个比较常用的用途,一个是可以利用它访问到局部变量,另一个是可以把它外围作用域中的变量值存储在内存中而不在函数调用完毕后就销毁。

下面接着看一个平淡无奇的例子,或许可以帮助理解为什么闭包可以把外部变量值保存在内存中了。

function makeFunc (x) { 
return function () {return x++} 
} 
var a = [makeFunc(0), makeFunc(1), makeFunc(2)]; 
alert(a[0]()); 
alert(a[1]()); 
alert(a[2]());

执行结果为0,1,2 ;也没有什么特别的地方,这也是严格的词法作用域的正常表现。每次makeFunc调用完毕后,它的调用对象会从作用域链中移除,再没有任何对它的引用,最终通过垃圾收集而完结。说的详细一点,我们可以这样理解。

makeFunc每次调用的时候,会为他创建一个调用对象放置到作用域链中。针对makeFunc这个函数而言,这个调用对象包含一个属性x(也就是函数的参数,因为函数参数可以看做调用对象的一个属性),makeFunc会返回一个匿名嵌套函数的引用,接下来这个匿名嵌套函数执行,又会创建一个调用对象,放置到作用域链中,匿名函数返回x的值,(注意:匿名函数的调用对象中是没有x的定义的,于是它会引用到它外围的函数makeFunc的调用对象,访问到x)然后x加1,至此,匿名函数执行完毕,它调用对象从作用域链中移除, 然后makeFunc也执行完毕,makeFunc调用对象也被移除。由于它的调用对象中包含x,所以x也随着它的销毁而销毁。不会保存下来。

以上就是函数的详细的执行过程,请仔细理解后看看下面改动的代码:

var x = 0; 
function makeFunc () { 
return function () {return x++} 
} 
var a = [makeFunc(), makeFunc(), makeFunc()]; 
alert(a[0]()); 
alert(a[1]()); 
alert(a[2]());

现在x是一个全局变量了,执行结果为0,1,2;但是这个结果就与上面的有些不同了。下面我们还是从作用域链的方向来理解这个结果产生的原因。

同样,makeFunc每次调用的时候会创建一个调用对象到作用域链中,由于它返回内部嵌套函数的引用,所以内部嵌套函数开始执行,又创建一个嵌套函数的调用对象到作用域链。然后返回x的值,注意,这里就不同了,嵌套函数的调用对象中没有x,它外围的makeFunc的调用对象中也没有x,只能接着往下查找到全局对象中,在全局对象中找到了x的定义,于是正常执行,返回x的值,x加1,然后嵌套函数完毕,调用对象移除,接着makeFunc完毕,调用对象也移除,可是因为他们的调用对象中都没有x,他们的调用对象销毁根本不会影响到x。于是,全局变量x值的改变就这样被保存下来了。

注意,上面说的访问外围的调用对象只是为了帮助理解而不严格的说法,JavaScript不会以任何方式直接访问调用对象,但是,它定义的属性作为调用对象中作用域链的一部分,还是“活的”。另外,如果一个外围函数包含了两个或多个嵌套函数都对全局对象有引用,那么这些嵌套函数都共享同一个全局调用对象,并且其中一个对全局对象的改变对其他的都是可见的。

好了,在JavaScript里,函数是将要执行的代码以及执行这些代码的作用域构成的一个综合体,广义的说,我们就可以把这种代码和作用域的综合体叫做闭包。

【闭包】

我们偶尔需要写一个需要通过调用来记住一个变量值的函数。于是,如果我们了解了作用域,就会知道,局部变量是很难做到的,因为函数的调用对象不能在调用后一直维持。全局变量可以做到,就如上面的例子一样,但是这样很容易造成全局变量污染。既然调用对象不能维持,那么我们不把值保存在调用对象中不就行了?!所以,下面是实现的一种方法:用函数对象自身的属性来保存。

uniqueID = function () { 
if (!arguments.callee.id) arguments.callee.id = 0; 
return arguments.callee.id ++; 
} 
alert(uniqueID()); //0 
alert(uniqueID()); //1

如上,因为函数本身就是一个对象,所以,我们用它自身的一个属性来保存是可行的,但是这样做有一个问题,就是任何人在任何时候都可以通过unqueID.id强制访问到我们原本保存到的值并作出修改。这是我们不愿看到的。

所以,通常,我们使用闭包来实现这件事。如下:

_uniqueID = (function(){ 
var id = 0; 
return function () {return id ++} 
})(); 
alert(_uniqueID()); //0 
alert(_uniqueID()); //1

同样,我们也用作用链域来解释下结果。注意到_uniqueID本身就是一个匿名函数,它内部又有个匿名嵌套函数,我们直接调用的是_uniqueID(),也就是说,我们直接调用的其实是_uniqueID内部的嵌套函数,而它本身的调用对象没有定义id,于是引用外围的调用对象中的id,并返回,id加1,执行完毕,内层嵌套函数调用对象移出作用域链。而外围的id并没有被销毁,于是就这样保存了下来。

有人可能会疑惑,不是说调用对象在函数执行完毕后就移除了作用域链吗,外围匿名函数(function(){})();也是调用完毕了的,应该调用对象也没了才对。

是的,调用对象是在当前函数执行完毕后就结束引用,但是这里不要误解了上面_uniqueID()的调用,他并不是直接调用的外围函数,而是调用的嵌套函数,嵌套函数的作用域链是包含外围函数的作用域链的。所以在它的调用对象移除作用域链的时候是能够访问到这条作用域链上其他对象的属性并改变的。

闭包本身就是个难以理解但是又非常有用的东西,希望能对有需要的人一些帮助吧。此外,资历所限,本人理解也可能有误,如发现,敬请指正。

Javascript 相关文章推荐
JavaScript打印iframe内容示例代码
Aug 20 Javascript
js history对象简单实现返回和前进
Oct 30 Javascript
jQuery如何取id有.的值一般的方法是取不到的
Apr 18 Javascript
微信小程序加载更多 点击查看更多
Nov 29 Javascript
JS实现的系统调色板完整实例
Dec 21 Javascript
如何在AngularJs中调用第三方插件库
May 21 Javascript
使用selenium抓取淘宝的商品信息实例
Feb 06 Javascript
详解操作虚拟dom模拟react视图渲染
Jul 25 Javascript
微信小程序签到功能
Oct 31 Javascript
浅谈vue中关于checkbox数据绑定v-model指令的个人理解
Nov 14 Javascript
JavaScript使用Math.random()生成简单的验证码
Jan 21 Javascript
vue下axios拦截器token刷新机制的实例代码
Jan 17 Javascript
JavaScript高级程序设计 扩展--关于动态原型
Nov 09 #Javascript
关于JavaScript定义类和对象的几种方式
Nov 09 #Javascript
JS图片浏览组件PhotoLook的公开属性方法介绍和进阶实例代码
Nov 09 #Javascript
一个javascript图片阅览组件
Nov 09 #Javascript
js中格式化日期时间型数据函数代码
Nov 08 #Javascript
window.location.hash 使用说明
Nov 08 #Javascript
JavaScript游戏之是男人就下100层代码打包
Nov 08 #Javascript
You might like
PHP下escape解码函数的实现方法
2010/08/08 PHP
php cli 小技巧
2013/06/03 PHP
PHP链表操作简单示例
2016/10/15 PHP
php实现当前页面点击下载文件的实例代码
2016/11/16 PHP
PHP+jQuery实现滚屏无刷新动态加载数据功能详解
2017/05/04 PHP
PHP7匿名类的用法示例
2019/04/05 PHP
北京奥运官方网站幻灯切换效果flash版打包下载
2008/01/30 Javascript
JQuery入门——用映射方式绑定不同事件应用示例
2013/02/05 Javascript
使用javascript实现监控视频播放并打印日志
2015/01/05 Javascript
js实现ctrl+v粘贴上传图片(兼容chrome、firefox、ie11)
2016/03/09 Javascript
简单理解vue中el、template、replace元素
2016/10/27 Javascript
Angular.js中处理页面闪烁的方法详解
2017/03/09 Javascript
解决微信二次分享不显示摘要和图片的问题
2017/08/18 Javascript
详解vue2.0 不同屏幕适配及px与rem转换问题
2018/02/23 Javascript
Vue动态控制input的disabled属性的方法
2018/06/26 Javascript
layer关闭弹出窗口触发表单提交问题的处理方法
2019/09/25 Javascript
解决vue项目input输入框双向绑定数据不实时生效问题
2020/08/05 Javascript
python判断图片宽度和高度后删除图片的方法
2015/05/22 Python
Python中list初始化方法示例
2016/09/18 Python
Python实现模拟登录网易邮箱的方法示例
2018/07/05 Python
Selenium定位元素操作示例
2018/08/10 Python
python数据挖掘需要学的内容
2019/06/23 Python
pytorch打印网络结构的实例
2019/08/19 Python
win10安装python3.6的常见问题
2020/07/01 Python
基于CentOS搭建Python Django环境过程解析
2020/08/24 Python
css3利用transform变形结合事件完成扇形导航
2020/10/26 HTML / CSS
CSS3选择器新增问题的实现
2021/01/21 HTML / CSS
html5 Canvas画图教程(9)—canvas中画出矩形和圆形
2013/01/09 HTML / CSS
关于HTML5+ API plusready的兼容问题
2020/11/20 HTML / CSS
美国巧克力喷泉品牌:Sephra
2019/05/05 全球购物
优秀体育委员自荐书
2014/01/31 职场文书
秦兵马俑教学反思
2014/02/07 职场文书
公司廉洁自律承诺书
2014/03/27 职场文书
工作证明英文模板
2014/10/21 职场文书
元旦晚会开场白
2015/05/29 职场文书
MySQL 视图(View)原理解析
2021/05/19 MySQL