位置: 编程技术 - 正文

跟我学习javascript的闭包(java教程)

编辑:rootadmin

推荐整理分享跟我学习javascript的闭包(java教程),希望有所帮助,仅作参考,欢迎阅读内容。

文章相关热门搜索词:java script课程,java教程 视,教程java,java learning,java script课程,java教程,java learning,java script,内容如对您有帮助,希望把文章链接给更多的朋友!

JavaScript 闭包究竟是什么&#;

用JavaScript一年多了,闭包总是让人二丈和尚摸不着头脑。陆陆续续接触了一些闭包的知识,也犯过几次因为不理解闭包导致的错误,一年多了资料也看了一些,但还是不是非常明白,最近偶然看了一下 jQuery基础教程 的附录,发现附录A对JavaScript的闭包的介绍简单易懂,于是借花献佛总结一下。

1、定义

闭包:是指有权访问另外一个函数作用域中的变量的函数。创建闭包的常见方式就是在一个函数内部创建另外一个函数。

直接上例子

这段代码有两个特点:

1)、函数b嵌套在函数a内部; 2)、函数a返回函数b。

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

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

2、闭包有什么作用?

简而言之,闭包的作用就是在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的垃圾回收机制将在后面详细介绍)

3、闭包内的微观世界

如果要更加深入的了解闭包以及函数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中的变量查找机制。

4、闭包的应用场景

1)、保护函数内的变量安全。以最开始的例子为例,函数a中i只有函数b才能访问,而无法通过其他途径访问到,因此保护了i的安全性。 2)、在内存中维持一个变量。依然如前例,由于闭包,函数a中i的一直存在于内存中,因此每次执行c(),都会给i自加1。 以上两点是闭包最基本的应用场景,很多经典案例都源于此。

5、Javascript的垃圾回收机制

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

在javascript中没有块级作用域,一般为了给某个函数申明一些只有该函数才能使用的局部变量时,我们就会用到闭包,这样我们可以很大程度上减少全局作用域中的变量,净化全局作用域。

使用闭包有如上的好处,当然这样的好处是需要付出代价的,代价就是内存的占用。

如何理解上面的那句话呢?

每个函数的执行,都会创建一个与该函数相关的函数执行环境,或者说是函数执行上下文。这个执行上下文中有一个属性 scope chain(作用域链指针),这个指针指向一个作用域链结构,作用域链中的指针又都指向各个作用域对应的活动对象。正常情况,一个函数在调用开始执行时创建这个函数执行上下文及相应的作用域链,在函数执行结束后释放函数执行上下文及相应作用域链所占的空间。

在调用函数的时候会在内存中生成如下图的结构:

但是闭包的情况就有点特殊了,由于闭包函数可以访问外层函数中的变量,所以外层函数在执行结束后,其作用域活动对象并不会被释放(注意,外层函数执行结束后执行环境和对应的作用域链就会被销毁),而是被闭包函数的作用域链所引用,直到闭包函数被销毁后,外层函数的作用域活动对象才会被销毁。这也正是闭包要占用内存的原因。

所以使用闭包有好处,也有坏处,滥用闭包会造成内存的大量消耗。

使用闭包还有其他的副作用,可以说是bug,也可以说不是,相对不同的业务可能就会有不同的看法。

这个副作用是闭包函数只能取到外层函数变量的最终值。

测试代码如下:(这里使用了jquery对象)

上面的代码先通过匿名函数表达式开辟了一块私有作用域,这个匿名函数就是我们上面所说的外层函数,该外层函数有一个参数$,同时还定义了变量result和 I , 通过for循环给数组result赋值一个匿名函数,这个匿名函数就是闭包,他访问了外层函数的变量I , 理论上数组resulti 会返回相应的数组下标值,实际情况却不如所愿。

如上代码 $.RES 的执行结果是.

为什么会这样呢,因为i的最终值就是.

下面我们通过下图来详细说明下,上面的那段代码执行时在内存中到底发生了什么:

跟我学习javascript的闭包(java教程)

那么这个副作用有没有办法可以修复呢?当然可以!

我们可以通过下面的代码来达到我们的预期。

上面的代码又在内存中发生了什么?我们同样用下面的一幅图来详细解释。看懂了上面的图,我们也就不难理解下面的图。

6.简单的例子

首先从一个经典错误谈起,页面上有若干个div, 我们想给它们绑定一个onclick方法,于是有了下面的代码

很简单的功能可是却偏偏出错了,每次alert出的值都是4,简单的修改就好使了

7.内部函数

让我们从一些基础的知识谈起,首先了解一下内部函数。内部函数就是定义在另一个函数中的函数。例如:

innerFn就是一个被包在outerFn作用域中的内部函数。这意味着,在outerFn内部调用innerFn是有效的,而在outerFn外部调用innerFn则是无效的。下面代码会导致一个JavaScript错误:

不过在outerFn内部调用innerFn,则可以成功运行:

8、伟大的逃脱(内部函数如何逃脱外部函数)

JavaScript允许开发人员像传递任何类型的数据一样传递函数,也就是说,JavaScript中的内部函数能够逃脱定义他们的外部函数。

逃脱的方式有很多种,例如可以将内部函数指定给一个全局变量:

调用outerFn时会修改全局变量globalVar,这时候它的引用变为innerFn,此后调用globalVar和调用innerFn一样。这时在outerFn外部直接调用innerFn仍然会导致错误,这是因为内部函数虽然通过把引用保存在全局变量中实现了逃脱,但这个函数的名字依然只存在于outerFn的作用域中。

也可以通过在父函数的返回值来获得内部函数引用

这里并没有在outerFn内部修改全局变量,而是从outerFn中返回了一个对innerFn的引用。通过调用outerFn能够获得这个引用,而且这个引用可以可以保存在变量中。

这种即使离开函数作用域的情况下仍然能够通过引用调用内部函数的事实,意味着只要存在调用内部函数的可能,JavaScript就需要保留被引用的函数。而且JavaScript运行时需要跟踪引用这个内部函数的所有变量,直到最后一个变量废弃,JavaScript的垃圾收集器才能释放相应的内存空间(红色部分是理解闭包的关键)。

说了半天总算和闭包有关系了,闭包是指有权限访问另一个函数作用域的变量的函数,创建闭包的常见方式就是在一个函数内部创建另一个函数,就是我们上面说的内部函数,所以刚才说的不是废话,也是闭包相关的 ^_^

9、变量的作用域

内部函数也可以有自己的变量,这些变量都被限制在内部函数的作用域中:

每当通过引用或其它方式调用这个内部函数时,就会创建一个新的innerVar变量,然后加1,最后显示

内部函数也可以像其他函数一样引用全局变量:

现在每次调用内部函数都会持续地递增这个全局变量的值:

但是如果这个变量是父函数的局部变量又会怎样呢?因为内部函数会引用到父函数的作用域(有兴趣可以了解一下作用域链和活动对象的知识),内部函数也可以引用到这些变量

这一次结果非常有意思,也许或出乎我们的意料

我们看到的是前面两种情况合成的效果,通过每个引用调用innerFn都会独立的递增outerVar。也就是说第二次调用outerFn没有继续沿用outerVar的值,而是在第二次函数调用的作用域创建并绑定了一个一个新的outerVar实例,两个计数器完全无关。

当内部函数在定义它的作用域的外部被引用时,就创建了该内部函数的一个闭包。这种情况下我们称既不是内部函数局部变量,也不是其参数的变量为自由变量,称外部函数的调用环境为封闭闭包的环境。从本质上讲,如果内部函数引用了位于外部函数中的变量,相当于授权该变量能够被延迟使用。因此,当外部函数调用完成后,这些变量的内存不会被释放(最后的值会保存),闭包仍然需要使用它们。

.闭包之间的交互

当存在多个内部函数时,很可能出现意料之外的闭包。我们定义一个递增函数,这个函数的增量为2

我们映射返回两个内部函数的引用,可以通过返回的引用调用任一个内部函数,结果:

innerFn1和innerFn2引用了同一个局部变量,因此他们共享一个封闭环境。当innerFn1为outerVar递增一时,久违innerFn2设置了outerVar的新的起点值,反之亦然。我们也看到对outerFn的后续调用还会创建这些闭包的新实例,同时也会创建新的封闭环境,本质上是创建了一个新对象,自由变量就是这个对象的实例变量,而闭包就是这个对象的实例方法,而且这些变量也是私有的,因为不能在封装它们的作用域外部直接引用这些变量,从而确保了了面向对象数据的专有性。

.解惑现在我们可以回头看看开头写的例子就很容易明白为什么第一种写法每次都会alert 4了。

上面代码在页面加载后就会执行,当i的值为4的时候,判断条件不成立,for循环执行完毕,但是因为每个span的onclick方法这时候为内部函数,所以i被闭包引用(闭包引用传的是引用),内存不能被销毁,i的值会一直保持4,直到程序改变它或者所有的onclick函数销毁(主动把函数赋为null或者页面卸载)时才会被回收。这样每次我们点击span的时候,onclick函数会查找i的值(作用域链是引用方式),一查等于4,然后就alert给我们了。

而第二种方式是使用了一个立即执行的函数又创建了一层闭包,函数声明放在括号内就变成了表达式,后面再加上括号括号就是调用了,这时候把i当参数传入,函数立即执行,num保存每次i的值。

这一通下来想必大家也和我一样,对闭包有所了解了吧,当然完全了解的话需要把函数的执行环境和作用域链搞清楚。

js实现获取div坐标的方法 本文实例讲述了js实现获取div坐标的方法。分享给大家供大家参考,具体如下:html中最常使用的控件就是div了,那么如何获取div的坐标呢如下方法可以实

跟我学习javascript的arguments对象 1、什么是argumentsarguments是是JavaScript里的一个内置对象,它很古怪,也经常被人所忽视,但实际上是很重要的。所有主要的js函数库都利用了arguments对象

跟我学习javascript的undefined与null 当讨论JavaScript中的原始数据类型时,大多数人都知道从String、Number到Boolean的基本知识。这些原始类型相当简单,行为符合常识。但是,本文将更多关注

标签: java教程

本文链接地址:https://www.jiuchutong.com/biancheng/384624.html 转载请保留说明!

上一篇:javascript伸缩型菜单实现代码(html伸缩布局)

下一篇:js实现获取div坐标的方法(js获取div下指定元素的值)

  • 计提房产税和土地税
  • 海关完税凭证是交给海关么
  • 城建税计税方法
  • 企业的免税收入用于支出所形成的费用
  • 个人社保部分公司承担可以入费用吗
  • 可辨认净资产和所有者权益的区别
  • 一般纳税人建筑工程税率是多少
  • 非货币投资如何缴纳企业所得税
  • 建筑工程类发票
  • 会计发票怎么样粘贴,记账凭证也要粘上吗
  • 材料亏损怎么做会计分录?
  • 工厂临时住宿
  • 应交税金明细账余额怎么算
  • 来料加工企业的会计处理问题
  • 什么情况下发票不能冲红
  • 税务局代开专用发票要缴纳城建税吗?
  • 仓储费用的增值税计入哪里
  • 保证金利息收入怎么开票
  • 股权转让能否低于股价
  • 外商企业需要交企业所得税吗
  • 记账凭证按照填列方式的分类包括
  • 购入增值税税控系统专用设备为什么全额计入固定资产
  • 最新土地增值税实施细则
  • 职工报销子女医药费
  • 资金筹集业务的账务处理重点笔记
  • 坏账核销后又收回的,增加坏账准备,并转入当期损益
  • 资产负债表中的固定资产怎么算
  • 融资租赁与经营租赁的区别主要是
  • cesium加载s3m
  • vue ng
  • uni app面试题
  • 房地产企业预缴税款最新规定
  • echarts简介
  • 其他收益会计科目怎么写
  • trace 命令用法
  • 腾讯产品面经
  • 27岁零基础转行做网络工程师
  • 房产原值是否含增值税
  • 农业合作社需要纳税吗
  • 一般纳税人专票认证抵扣流程
  • 固定资产进项税额怎么抵扣
  • 价税合计公式计算
  • 新会计准则套期利息计算
  • 专款专用需要缴税吗
  • 待报解预算收入什么意思
  • 红字转账凭证怎样录入
  • 错账更正方法有几种分别适用于什么情况
  • 增值税期末留抵退税原因采集确认单
  • 出口报关金额怎么算
  • 提取备用金现金流量如何填写
  • linux文件系统损坏
  • java异常后面的语句会运行吗
  • windows7与xp共享文件夹
  • windowsxp停止服务的时间
  • 如何延迟windows更新
  • removed.exe - removed是什么进程 有什么用
  • win8系统特点
  • Win10如何打开软键盘
  • 电脑开机显示xp后无反应
  • win7开机提示音在哪里关闭
  • win7网络连接不显示
  • linux 排序统计
  • windows7库功能
  • linux 清除垃圾
  • 3dmax创建图形怎么用
  • Cocos2dx 3.2 + vs2012 + win7 改变面黑色背景的大小
  • node 获取当前时间
  • dos命令批量处理文件
  • js按照指定顺序排序
  • javascrapt
  • python运算符的用法
  • node.js tcp 服务器
  • shell脚本生成文件
  • javascript学习指南
  • jQuery实现获取table表格第一列值的方法
  • 湖北税务官方网
  • 建筑工地临时板房验收规范最新
  • 增值税申报表如何更正
  • 合格的税务人
  • 主要领导负责人
  • 免责声明:网站部分图片文字素材来源于网络,如有侵权,请及时告知,我们会第一时间删除,谢谢! 邮箱:opceo@qq.com

    鄂ICP备2023003026号

    网站地图: 企业信息 工商信息 财税知识 网络常识 编程技术

    友情链接: 武汉网站建设