这篇文章其实是要讲闭包的一些初级应用,但是为了将闭包,我们还是从嵌套函数开始说吧,纵使所有的JavaScript函数都可以说是闭包,但是只有当一个嵌套函数被导出到它所定义的作用域之外时,这种闭包才是有趣的。

  【嵌套函数】
  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()的调用,他并不是直接调用的外围函数,而是调用的嵌套函数,嵌套函数的作用域链是包含外围函数的作用域链的。所以在它的调用对象移除作用域链的时候是能够访问到这条作用域链上其他对象的属性并改变的。

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

转载于:https://www.cnblogs.com/hongru/archive/2010/10/30/1865041.html

浅谈Javascript -- 【嵌套函数及闭包】相关推荐

  1. 浅谈javascript中原型(prototype)、构造函数、对象实例及三者之间的关系

    转自:http://www.cnblogs.com/zhangwei412827/archive/2012/12/14/2816263.html 浅谈javascript中原型(prototype). ...

  2. 浅谈JavaScript作用域,关于Java的学习路线资料

    javascript是目前web领域中使用非常广泛的语言,不管是在前端还是在后端都能看到它的影子,可以说web从业者不论怎样都绕不开它.在前端领域,各种框架层出不穷.在后端领域,nodejs可谓如火如 ...

  3. 浅谈 JavaScript 编程语言的编码规范--转载

    原文:http://www.ibm.com/developerworks/cn/web/1008_wangdd_jscodingrule/ 对于熟悉 C/C++ 或 Java 语言的工程师来说,Jav ...

  4. JavaScript 中的 require / exports、import / export、浅谈JavaScript、ES5、ES6

    Node.js 的基础教学 之 exports 和 module.exports:https://zhuanlan.zhihu.com/p/82057593 浅谈 JavaScript.ES5.ES6 ...

  5. 浅谈 JavaScript 编程语言的编码规范

    转自:http://www.ibm.com/developerworks/cn/web/1008_wangdd_jscodingrule/?ca=drs-tp4608 developerWorks 中 ...

  6. python中怎么调用函数_浅谈Python中函数的定义及其调用方法

    一.函数的定义及其应用 所谓函数,就是把具有独立功能的代码块组织成为一个小模块,在需要的时候调用函数的使用包含两个步骤 1.定义函数–封装独立的功能 2.调用函数–享受封装的成果 函数的作用:在开发时 ...

  7. python函数定义及调用-浅谈Python中函数的定义及其调用方法

    一.函数的定义及其应用 所谓函数,就是把具有独立功能的代码块组织成为一个小模块,在需要的时候调用函数的使用包含两个步骤 1.定义函数�C封装独立的功能 2.调用函数�C享受封装的成果 函数的作用:在开 ...

  8. 【浅谈】main函数的三个参数

    [浅谈]main函数的参数 我们平时使用main函数时,在main函数参数这一栏常常省略不写,或者填上(void).实际上main函数是具有参数的,并且它的参数有很大作用.今天我们来看看main函数的 ...

  9. html 滚动条 scrolltop scrollheight,浅谈JavaScript中scrollTop、scrollHeight、offsetTop、offsetHeight...

    浅谈JavaScript中scrollTop.scrollHeight.offsetTop.offsetHeight 发布时间:2020-07-17 09:27:20 来源:亿速云 阅读:223 作者 ...

  10. 浅谈JavaScript中的NaN

    浅谈JavaScript中的NaN NaN概念以及简单案例 追寻的纯粹该拥有自己的本质.-JC.F 什么是NaN? NaN:NaN(Not a Number),它表示不是数字,但是仍是数值类型. Na ...

最新文章

  1. 北斗导航 | Matlab实现GNSS接收机经纬度数据(NMEA语句)实时获取和地图绘制(Matlab源代码)
  2. Win32ASM代码基本模块
  3. [emacs] 用 ido 或 anything 进行选择
  4. 中汽中心软件测评中心与紫光国微达成芯片认证合作
  5. 使用struts2来防止表单重复提交
  6. Taurus入门,这么神奇的性能测试工具不能不知道
  7. 【数据结构笔记33】C实现:希尔排序、增量序列
  8. Windows驱动开发,几个BLOG值得经常看看
  9. mysql加begin报错,MySQL存储过程例子,不能在if else里面用begin end否则会报错Error Code:1064解决...
  10. 包含min函数的栈 【微软面试100题 第二题】
  11. python破解wifi-Python利用字典破解WIFI密码的方法
  12. VMware虚拟机安装win10卡顿优化
  13. mysql odbc 免安装_MySQL免安装版配置
  14. word中html在哪,如何word中显示部分的域代码在哪
  15. “数据打通”不等于“数据共融”,智能数据营销解决方案了解一下
  16. mac环境下cocos2dx引擎3.x版本的创建工程步骤
  17. 纸板表的全球与中国市场2022-2028年:技术、参与者、趋势、市场规模及占有率研究报告
  18. CSS-三栏布局新手上路
  19. php base64encode 长度,base64_encode
  20. 如何用word制作英语答题卡_初中英语考试答题卡(可编辑WORD版)

热门文章

  1. 避坑指南!数据分析最容易被忽略的10个错误
  2. 32岁领导的忠告:别把报表不当回事,早点放弃Excel才是出路
  3. 不融资,如何在大数据BI市场占据一席之地
  4. 基于用户长短期表示的新闻推荐
  5. python命令行进入帮助模式_Python 命令行之旅:深入 click 之选项篇
  6. java 字符串常用函数_Java学习笔记35:Java常用字符串操作函数
  7. STL常用函数总结-set
  8. python数据结构之元组(tuple)——超详细
  9. 《南溪的目标检测学习笔记》——backbone的学习笔记
  10. git删除本地tag和远程tag