JavaScript中的闭包(Closure)详细介绍


Posted in Javascript onDecember 30, 2014

闭包是JavaScript中一个重要的特性,其最大的作用在于保存函数运行过程中的信息。在JavaScript中,闭包的诸多特性源自函数调用过程中的作用域链上。

 

函数调用对象与变量的作用域链

 

对于JavaScript中的每一次函数调用,JavaScript都会创建一个局部对象以储存在该函数中定义的局部变量;如果在该函数内部还有一个嵌套定义的函数(nested function),那么JavaScript会在已经定义的局部对象之上再定义一个嵌套局部对象。对于一个函数,其内部有多少层的嵌套函数定义,也就有多少层的嵌套局部对象。该局部对象称为“函数调用对象”(ECMAScript 3中的“call object”,ECMAScript 5中改名为“declarative environment record”,但个人认为还是ECMAScript 3中的名称更容易理解一些)。以下面的函数调用为例:

function f(x){

  var a = 10;

  return a*x;

}

console.log(f(6));//60

在这个简单的例子中,当调用f()函数时,JavaScript会创建一个f()函数的调用对象(姑且称之为f_invokeObj),在f_invokeObj对象内部有两个属性:a和x;运行f()时,a值为10而x值为6,因此最后的返回结果为60。图示如下:

JavaScript中的闭包(Closure)详细介绍

当存在函数嵌套时,JavaScript将创建多个函数调用对象:

function f(x){

  var a = 10;

  return a*g(x);

  function g(b){

    return b*b;

  }

}

console.log(f(6));//360

在这个例子中,当调用f()函数时,JavaScript会创建一个f()函数的调用对象(f_invokeObj),其内部有两个属性a和x,a值为10而x值为6;运行f()时,JavaScript会对f()函数中的g()函数进行解析定义,并创建g()的调用对象(g_invokeObj),其内部有一个属性b,b值与传入参数x相同为6,因此最后的返回结果为360。图示如下:

JavaScript中的闭包(Closure)详细介绍

可以看到,函数调用对象形成了一条链。当内嵌函数g()运行,需要获取变量值的时候,会从最近的函数调用对象中开始进行搜索,如果无法搜索到,则沿函数调用对象链在更远的调用对象中进行搜寻,此即所谓的“变量的作用域链”。如果两个函数调用对象中出现相同的变量,则函数会取离自己最近的那个调用对象中的变量值:

function f(x){

  var a = 10;

  return a*g(x);

  function g(b){

    var a = 1;

    return b*b*a;

  }

}

console.log(f(6));//360, not 3600

在上面的例子中,g()函数的调用对象(g_invokeObj)和f()函数的调用对象(f_invokeObj)中均存在变量a且a的值不同,当运行g()函数时,在g()函数内部所使用的a值为1,而在g()函数外部所使用的a值则为10。图示此时的函数调用对象链如下:

JavaScript中的闭包(Closure)详细介绍

什么是闭包?

在JavaScript中所有的函数(function)都是对象,而定义函数时都会产生相应的函数调用对象链,一次函数定义对应一个函数调用对象链。只要函数对象存在,相应的函数调用对象就存在;一旦某函数不再被使用,相应的函数调用对象就会被垃圾回收掉;而这种函数对象和函数调用对象链之间的一一组合,就称之为“闭包”。在上面f()函数和g()函数的例子中,就存在两个闭包:f()函数对象和f_invokeObj对象组成了一个闭包,而g()函数对象和g_invokeObj-f_invokeObj对象链一起组成了第二个闭包。当g()函数执行完毕后,由于g()函数不再被使用,因此g()闭包被垃圾回收了;之后,当f()函数执行完毕后,由于同样的原因,f()闭包也被垃圾回收了。

从闭包的定义可以得出结论:所有的JavaScript函数在定义后都是闭包 ? 因为所有的函数都是对象,所有的函数在执行后也都有其对应的调用对象链。

不过,令闭包真正发挥作用的是嵌套函数的情况。由于内嵌函数是在外部函数运行的时候才开始定义的,因此内嵌函数的闭包中所保存的变量值(尤其是外部函数的局部变量值)是这次运行过程中的值。只要内嵌函数对象依然存在,那么其闭包就依然存在(闭包中的变量值不会发生任何改变),从而也就实现了保存函数运行过程的信息这个目的。考虑以下这个例子:

var a = "outside";

function f(){

  var a = "inside";

  function g(){return a;}

  return g;

}

var result = f();

console.log(result());//inside

在这个例子中,当运行f()函数时,g()函数被定义,同时创建了g()函数的闭包,g()闭包包含了g_invokeObj-f_invokeObj对象链,因此保存了f()函数执行过程中的变量a的值。当执行console.log()语句时,由于g函数对象仍然存在,因此g()闭包也依然存在;当运行这个仍然存在的g函数对象时,JavaScript会使用依然存在的g()闭包并从中获取变量a的值(“inside”)。

Javascript 相关文章推荐
JavaScript 全面解析各种浏览器网页中的JS 执行顺序
Feb 17 Javascript
jQuery+css实现的时钟效果(兼容各浏览器)
Jan 27 Javascript
Jquery调用iframe父页面中的元素及方法
Aug 23 Javascript
详解在Vue中如何使用axios跨域访问数据
Jul 07 Javascript
ES6中的rest参数与扩展运算符详解
Jul 18 Javascript
React router动态加载组件之适配器模式的应用详解
Sep 12 Javascript
TypeScript基础入门教程之三重斜线指令详解
Oct 22 Javascript
JS实现的Object数组去重功能示例【数组成员为Object对象】
Feb 01 Javascript
Vue侦测相关api的实现方法
May 22 Javascript
jQuery模仿ToDoList实现简单的待办事项列表
Dec 30 jQuery
Vue elementui字体图标显示问题解决方案
Aug 18 Javascript
vant-ui AddressEdit地址编辑和van-area的用法说明
Nov 03 Javascript
JavaScript中的类(Class)详细介绍
Dec 30 #Javascript
JavaScript实现防止网页被嵌入Frame框架的代码分享
Dec 29 #Javascript
jQuery实现ichat在线客服插件
Dec 29 #Javascript
jQuery中用dom操作替代正则表达式
Dec 29 #Javascript
jQuery中:animated选择器用法实例
Dec 29 #Javascript
纯JavaScript实现获取onclick、onchange等事件的值
Dec 29 #Javascript
JavaScript实现列出数组中最长的连续数
Dec 29 #Javascript
You might like
php简单获取文件扩展名的方法
2015/03/24 PHP
typecho插件编写教程(二):写一个新插件
2015/05/28 PHP
PHP环境中Memcache的安装和使用
2015/11/05 PHP
jQuery图片预加载 等比缩放实现代码
2011/10/04 Javascript
jquery 中多条件选择器,相对选择器,层次选择器的区别
2012/07/03 Javascript
点弹代码 点击页面任何位置都可以弹出页面效果代码
2012/09/17 Javascript
Javascript 运动中Offset的bug解决方案
2014/12/24 Javascript
JS基于面向对象实现的拖拽功能示例
2016/12/20 Javascript
AngularJS中使用ngModal模态框实例
2017/05/27 Javascript
超级简易的JS计算器实例讲解(实现加减乘除)
2017/08/08 Javascript
通俗解释JavaScript正则表达式快速记忆
2017/08/23 Javascript
JavaScript捕捉事件和阻止冒泡事件实例分析
2018/08/03 Javascript
快速解决bootstrap下拉菜单无法隐藏的问题
2018/08/10 Javascript
解决webpack dev-server不能匹配post请求的问题
2018/08/24 Javascript
如何安装控制器JavaScript生成插件详解
2018/10/21 Javascript
在layer弹层layer.prompt中,修改placeholder的实现方法
2019/09/27 Javascript
JavaScript简单编程实例学习
2020/02/14 Javascript
微信小程序scroll-view实现滚动到锚点左侧导航栏点餐功能(点击种类,滚动到锚点)
2020/06/11 Javascript
[50:01]Ti4 冒泡赛第二天 NEWBEE vs Titan
2014/07/15 DOTA
Python入门篇之编程习惯与特点
2014/10/17 Python
跟老齐学Python之折腾一下目录
2014/10/24 Python
Python格式化字符串f-string概览(小结)
2019/06/18 Python
CSS3美化表单控件全集
2016/06/29 HTML / CSS
Html5移动端div固定到底部实现底部导航条的几种方式
2021/03/09 HTML / CSS
沪江旗下的海量优质课程平台:沪江网校
2017/11/07 全球购物
有模特经验的简历自我评价
2013/09/19 职场文书
创先争优承诺书范文
2014/03/31 职场文书
村党支部群众路线教育实践活动对照检查材料
2014/09/26 职场文书
道路交通事故人身损害赔偿协议书
2014/11/19 职场文书
2014年保洁工作总结
2014/11/24 职场文书
毕业生评语大全
2015/01/04 职场文书
2015年高中语文教学总结
2015/08/18 职场文书
外出培训学习心得体会
2016/01/18 职场文书
Python趣味挑战之实现简易版音乐播放器
2021/05/28 Python
springboot使用Redis作缓存使用入门教程
2021/07/25 Redis
MySQL学习之基础命令实操总结
2022/03/19 MySQL