深入理解Javascript闭包

 

 收藏

 最近在网上查阅了不少Javascript闭包(closure)相关的资料,写的大多是非常的学术和专业。对于初学者来说别说理解闭包了,就连文字叙述都很难看懂。撰写此文的目的就是用最通俗的文字揭开Javascript闭包的真实面目。

  一、什么是闭包?

  “官方”的解释是:所谓“闭包”,指的是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分。

  相信很少有人能直接看懂这句话,因为他描述的太学术。我想用如何在Javascript中创建一个闭包来告诉你什么是闭包,因为跳过闭包的创建过程直接理解闭包的定义是非常困难的。看下面这段代码:

    function a(){
      var i=0;
      function b(){
        alert(++i);
      }
      return b;
    }
    var c = a();
    c();

  这段代码有两个特点:

  1、函数b嵌套在函数a内部;

  2、函数a返回函数b。

  这样在执行完var c=a()后,变量c实际上是指向了函数b,再执行c()后就会弹出一个窗口显示i的值(第一次为1)。这段代码其实就创建了一个闭包,为什么?因为函数a外的变量c引用了函数a内的函数b,就是说:

  当函数a的内部函数b被函数a外的一个变量引用的时候,就创建了一个闭包。

  我猜想你一定还是不理解闭包,因为你不知道闭包有什么作用,下面让我们继续探索。

  二、闭包有什么作用?

  简而言之,闭包的作用就是在a执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a所占用的资源,因为a的内部函数b的执行需要依赖a中的变量。这是对闭包作用的非常直白的描述,不专业也不严谨,但大概意思就是这样,理解闭包需要循序渐进的过程。

在上面的例子中,由于闭包的存在使得函数a返回后,a中的i始终存在,这样每次执行c(),i都是自加1后alert出i的值。

  那 么我们来想象另一种情况,如果a返回的不是函数b,情况就完全不同了。因为a执行完后,b没有被返回给a的外界,只是被a所引用,而此时a也只会被b引 用,因此函数a和b互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。(关于Javascript的垃圾回收机制将在后面详细介绍)

  三、闭包内的微观世界

  如 果要更加深入的了解闭包以及函数a和嵌套函数b的关系,我们需要引入另外几个概念:函数的执行环境(excution context)、活动对象(call object)、作用域(scope)、作用域链(scope chain)。以函数a从定义到执行的过程为例阐述这几个概念。

  1、当定义函数a的时候,js解释器会将函数a的作用域链(scope chain)设置为定义a时a所在的“环境”,如果a是一个全局函数,则scope chain中只有window对象。

  2、当函数a执行的时候,a会进入相应的执行环境(excution context)。

  3、在创建执行环境的过程中,首先会为a添加一个scope属性,即a的作用域,其值就为第1步中的scope chain。即a.scope=a的作用域链。

  4、然后执行环境会创建一个活动对象(call object)。活动对象也是一个拥有属性的对象,但它不具有原型而且不能通过JavaScript代码直接访问。创建完活动对象后,把活动对象添加到a的作用域链的最顶端。此时a的作用域链包含了两个对象:a的活动对象和window对象。

  5、下一步是在活动对象上添加一个arguments属性,它保存着调用函数a时所传递的参数。

  6、最后把所有函数a的形参和内部的函数b的引用也添加到a的活动对象上。在这一步中,完成了函数b的的定义,因此如同第3步,函数b的作用域链被设置为b所被定义的环境,即a的作用域。

  到此,整个函数a从定义到执行的步骤就完成了。此时a返回函数b的引用给c,又函数b的作用域链包含了对函数a的活动对象的引用,也就是说b可以访问到a中定义的所有变量和函数。函数b被c引用,函数b又依赖函数a,因此函数a在返回后不会被GC回收。

  当函数b执行的时候亦会像以上步骤一样。因此,执行时b的作用域链包含了3个对象:b的活动对象、a的活动对象和window对象,如下图所示:

  如图所示,当在函数b中访问一个变量的时候,搜索顺序是先搜索自身的活动对象,如果存在则返回,如果不存在将继续搜索函数a的活动对象,依 次查找,直到找到为止。如果整个作用域链上都无法找到,则返回undefined。如果函数b存在prototype原型对象,则在查找完自身的活动对象 后先查找自身的原型对象,再继续查找。这就是Javascript中的变量查找机制。

  四、闭包的应用场景

  1、保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。

  2、在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。

  以上两点是闭包最基本的应用场景,很多经典案例都源于此。

  五、Javascript的垃圾回收机制

  在Javascript中,如果一个对象不再被引用,那么这个对象就会被GC回收。如果两个对象互相引用,而不再被第3者所引用,那么这两个互相引用的对象也会被回收。因为函数a被b引用,b又被a外的c引用,这就是为什么函数a执行后不会被回收的原因。

转载于:https://www.cnblogs.com/wenluren/archive/2011/05/27/2060025.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/405250.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

移动Oracle的用户表空间文件方法

原文:http://www.linuxidc.com/Linux/2014-07/104702.htm 1、以sys用户登录 sqlplus /nologSQL>connect sys/null as sysdba; 2、使表空间脱机SQL>alter tablespace TABS offline normal; 3、将表空间数据文件复制到比较空闲的磁盘 4、修改表空间文件指…

bash删除文件中含指定内容的行

#!/bin/sh # 功能: 删除文件中含"指定内容"的行 # 运行方式: ./dline.sh c.log > 产生输出文件: c.log0array("rm -f lvr_3531_pf_new""arm-hisiv100-linux-gcc ""In function ""excess elements in array initializer"…

四线电阻屏

http://www.cnblogs.com/liu_xf/archive/2011/05/11/2043550.html

树莓派使用STEP4:安装vim

系统原装的vi操作对新手和学习者不友好,可以用nano编辑器,因为我比较熟悉vi和vim,这里推荐使用vim。首先需要删除原装的vi,然后重新安装新的vim,过程比较简单。 1、卸载预装的vi sudo apt-get remove vim-common 2、…

[WCF安全系列]绑定、安全模式与客户端凭证类型:NetNamedPipeBinding、NetTcpBinding与NetMsmqBinding...

在前面两篇(《绑定、安全模式与客户端凭证类型:BasicHttpBinding》和《绑定、安全模式与客户端凭证类型:WSHttpBinding与WSDualHttpBinding》)中,我们详细地介绍了四种基于HTTP的绑定分别支持的安全模式,已…

手机测试pc端网页

在这个问题上徘徊了 一个钟头了,终于被我找到方法了,就赶紧记下来,以后好查阅!! 主要问题在防火墙,防火墙阻当了80端口,所以怎么用手机访问都是访问不了的。把防火墙关闭就好了! 贴上…

[react] 为什么说React中的props是只读的?

[react] 为什么说React中的props是只读的? React 组件都必须像纯函数一样保护它们的 props 不被更改。 将react组件理解成纯函数,数据流驱动,参数传入不允许做更改 扩展 : state内容可以更改,但是不允许直接赋值,需要借助setState props用于定义外部接口&#xff0c…

[bash] printf使用范例

#!/bin/shfunction get_file_size() {line${1:1:${#1}-2}fname${line%%:*} # 取:之前的部分size${line#* } # 取 之后的部分 }function getRuntime() {line${1#*}rtime${line//[^0-9.]/} }dos2unix log.log > /dev/null 2>&1printf "序号\t\t录像文件\t大小(KB…

树莓派使用STEP5:安装samba文件共享服务器

samba服务器可以在多平台多操作系统搭建文件服务器,用于共享文件。为了方便windows和树莓派交换文件,将samba服务器的搭建过程记录如下。 1、安装samba服务器。 sudo apt-get install samba samba-common-bin 出现以下提示:Modify smb.conf…

Silverlight学习日记(三)

初学Silverlight记录下一些基础知识; 应用程序生存期管理 你可以在应用程序生存期的以下各点向应用程序类添加代码: 1.应用程序类构造函数; 您可以向应用程序类构造函数添加代码,以执行基本初始化任务,例如&#xff0c…

ETH—Lwip以太网通信

第39章 ETH—Lwip以太网通信 全套200集视频教程和1000页PDF教程请到秉火论坛下载:www.firebbs.cn 野火视频教程优酷观看网址:http://i.youku.com/firege 互联网技术对人类社会的影响不言而喻。当今大部分电子设备都能以不同的方式接入互联网(Inter…

递归删除指定目录下的指定类型文件和目录

del.bat: echo offset olddir%cd% set pwdD:\Desktop\platform_x2 cd %pwd% del /s /q *.o *.d for /r %pwd% %%a in (.svn) do rd /s/q "%%a" cd %olddir%上面的脚本实现了删除指定目录下的 所有*.o, *.d文件,以及 所有".svn"目录。

计算从A地出发到各个地方的路径及距离

数据库环境:SQL SERVER 2005 如题,现有bus表数据如下,dstart是起点,dend是终点,distance是两地的距离。 求从A地出发到各个地方的距离。 有经验的人一看,就知道题目关于树形查询的。SQL SERVER 2005数据库…

发送邮件时,如何附带上中文等价名信息

1******发送人在邮箱中显示中文等价名****** 2Dim item As NotesItem 3Dim v As Variant 4Dim s As String 5Set item maildoc.AppendItemValue( "$LangFrom", "zh-CN" ) 给$LangFrom赋值 6s"UserNamesList" 7vEvaluate(s) 8maildoc.AltFromv…

树莓派使用STEP6:安装git

git用于创建和管理代码仓,是一个很优秀的版本控制工具。linux/树莓派安装非常简单。 1、sudo apt-get install git-core

[react] 如果组件的属性没有传值,那么它的默认值是什么?

[react] 如果组件的属性没有传值,那么它的默认值是什么? 如果某一个prop并没有初始化值,那么在没有设置默认值的情况下,是会直接返回undefined的,如果有默认值,那么就会返回默认值 个人简介 我是歌谣&am…

cJSON 使用笔记

缘 起 最近在stm32f103上做一个智能家居的项目,其中选择的实时操作系统是 rt_thread OS v1.2.2稳定版本,其中涉及到C和java(android)端数据…

使用虚拟路径时出现404问题

今天在做一个小项目的时候使用了如下路径 web.xml如下: 一切配置都正确,可还是404 在折腾了半天之后发现浏览器的地址栏没有项目名!也就是说连接到项目外面去了,果断404。然后才记得虚拟路径前面的“/”和通常用的“../”具有同样…

运行shell:windows命令,及显示桌面.scf的问题

From: http://hi.baidu.com/ser163/item/88e69513149a859f98ce3335 “显示桌面.scf”内容是: [Shell] Command2 IconFileexplorer.exe,3 [Taskbar] CommandToggleDesktop (把上面文字粘贴到记事本里,保存为“显示桌面.scf”即可) 开始&#…

[react] render方法的原理你有了解吗?它返回的数据类型是什么?

[react] render方法的原理你有了解吗?它返回的数据类型是什么? render的第一个参数通过babel转为React.createElement,后者根据参数类型的不同调用不同的内部方法来转换为原生dom并生成真实dom插入到容器中. 对原生html标签调用ReactDOMCom…