JavaScript闭包

1.函数在JavaScript中的地位

在介绍闭包之前,可以先聊聊函数在JavaScript中的地位,因为闭包的存在是与函数息息相关的。

  • JavaScript之所以可以称之为支持头等函数的编程语言,是因为JavaScript中函数是一等公民
  • 函数不仅在JavaScript中扮演着重要的角色,而且可以使用的非常灵活;
  • 函数不仅可以作为另一个函数的参数,也可以作为另一个函数的返回值
  • 这样使用的函数也称之为高阶函数,像JS的数组中就实现了许多高阶函数(map、filter、reduce等);

2.JavaScript中闭包的定义

闭包的概念出现于60年代,最早实现闭包的程序是Scheme,那么就可以理解为什么JavaScript中有闭包了,因为JavaScript中大量的设计是源自于Scheme的。而在不同的地方对JavaScript闭包的定义是不一样的,但是整体核心还是一致的,只是用不同的话来描述JavaScript闭包,以下是摘抄自三个地方的定义。

维基百科中对闭包的定义:

  • 闭包(Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中,实现词法绑定的一种技术;
  • 闭包在实现上是一个结构体,它存储了一个函数一个关联的环境(相当于一个符号查找表);
  • 闭包跟函数最大的区别在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行;

MDN中对闭包定义:

  • 一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure);
  • 也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域
  • 在JavaScript中,每当创建一个函数,闭包就会在函数创建的同时被创建出来;

《JavaScript高级程序设计》中对闭包的定义:

  • 闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的;

对闭包定义的总结:

  • 以上对闭包的三种定义,都提到了函数、环境、作用域和变量,总结为就是:一个函数,如果它可以访问外层作用域自由变量,那么这个函数就是一个闭包;
  • 闭包由两部分组成:内层函数+可以访问的外层自由变量
  • 广义角度:JavaScript中的函数都是闭包(都可以形成闭包);
  • 狭义角度:JavaScript中的一个函数,如果访问了外层作用域的变量,那么它是一个闭包;

3.闭包是如何形成的?

看了一大堆闭包的定义,那么到底什么情况下就形成了闭包呢?

(1)产生闭包的条件:简单来说,满足以下几个条件就可以说产生了闭包。

  • 函数嵌套;
  • 内层函数引用了外层函数作用域中的变量;
  • 外层函数执行;

(2)常见的闭包。

  • 将一个函数作为另一个函数返回值,例如:

    function foo() {
    var name = 'foo' return function bar() {
    console.log(name)
    }
    } var fn = foo()
    fn()
  • 将一个函数作为实参传递另一个函数,例如:

    function showDelay(msg) {
    setTimeout(function() {
    console.log(msg)
    })
    } showDelay('我形成了闭包')

4.闭包的访问和执行过程

下面介绍闭包在访问和执行过程中的内存表现,进一步深入对闭包的了解,以如下代码为例:

示例代码:

function foo() {
var name = 'foo' return function bar() {
console.log(name)
}
} var fn = foo()
fn()
  • 首先,在执行全局代码之前,会在内存中创建一个全局对象(GO),将全局执行上下文压入栈中,这时的fn还未被赋值;

  • 当执行到var fn = foo()时,在调用foo之前创建foo的活动对象(AO),创建foo函数执行上下文,并将其压入栈中,接着执行foo函数,执行完成后fn指向bar函数内存地址;

  • foo函数执行完成后,foo函数执行上下文会弹出栈,而按道理foo的活动对象(AO)是需要被销毁的,那到底有没有销毁,我们接着看;

  • 接着执行fn(),因为fn是指向bar函数的,执行之前会先创建bar的活动对象(AO),然后执行console.log(name),而name会先去自己的AO中查找,发现没有找到就会去到上层作用域(父级作用域)中查找,最终找到foo并打印,这里bar函数的上层作用域就是foo函数的作用域对应foo的活动对象(AO);

  • bar函数执行完成后,bar函数的执行上下文弹出栈,对应bar的活跃对象(AO)被销毁,而foo的活跃对象(AO)还一直存留在内存中;

  • 但是bar函数的父级作用域是在什么时候确定的呢?

    • 在编译bar函数时就已经确定了bar函数的父级作用域——foo的活动对象(AO)早在编译时就加入到了bar函数的作用域链中;
    • 为什么执行完foo函数后还可以访问其name变量,就可以回答上面的问题了,foo的活动对象(AO)是没有被销毁的;
    • 因为bar函数的作用域链中依然对foo的活动对象(AO)有引用,导致其不能正常销毁;

根据上面闭包的访问和执行过程结合闭包的定义做一个总结:

  • 在维基百科中定义的“闭包在实现上是一个结构体,它存储了一个函数和一个关联的环境(相当于一个符号查找表)”,以及MDN中定义的“一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)”,其函数和关联的环境、函数和对其周围状态的引用,对应的就是上面的bar函数和上层作用域中的name;

  • 而对于维基百科中提到的“闭包跟函数最大的区别在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即使脱离了捕捉时的上下文,它也能照常运行”,也就是在捕捉bar函数时,同时捕捉到对name这个自由变量的引用,执行完foo函数后,将bar函数赋值给fn,最后执行fn时,也是能正常访问到name的;

  • 了解了其访问执行过程后,可以发现本应该被销毁的foo的活跃对象(AO),在代码执行完后最终没能被销毁,而这样的情况称之为内存泄露,下面就来谈谈闭包的内存泄露;

  • 如果对上面的执行过程不清楚,可以先看看这篇文章:JavaScript的执行过程(深入执行上下文、GO、AO、VO和VE等概念)

5.闭包的内存泄露

闭包会保留它们包含函数的作用域,所以比其它函数更占用内存,而过渡使用闭包可能导致内存过度占用,也就是内存泄露,而除了内存泄露这个概念还有一个内存溢出的概念,下面就先了解一下这两者的区别和关系:

  • 内存溢出:程序运行出现错误,当程序运行需要的内存超过了剩余的内存时,就会抛出内存溢出的错误(比如,死循环)。
  • 内存泄露:占用的内存没有及时释放,内存泄露积累过多就容易导致内存溢出(比如,意外的全局变量、没有及时清理计时器或回调、闭包

那具体怎么解决闭包产生的内存泄露呢?

  • 针对于上面的代码,可以在最后执行fn = null
  • 因为将fn设置为null时,就不再对bar函数有引用,bar函数失去了全部的引用就会被销毁,对应foo的活动对象(AO)也就失去了引用,在下一次的垃圾回收(GC)检测中,就会被销毁掉;

6.使用浏览器查看闭包

闭包其实是可以在浏览器中观察到的,在查看闭包之前先来讨论一个问题,外层函数的活跃对象(AO)不会被销毁,是不是里面所有的属性都不会被销毁呢?

如果将上面的代码改成下面这样,多增加两个变量age和message,但是bar函数中并没有对age和message有引用:

function foo() {
var name = 'foo'
var age = 18
var message = 'hello bibao' return function bar() {
console.log(name)
}
} var fn = foo()
fn()
  • 形成闭包后,name是一定不会被销毁的,这个上面已经验证过了;

  • 具体age和message有没有被销毁,可以在代码中打上断点,在Chrome浏览器查看对应的闭包;

  • 观察上面的结果是没有age和message属性的,这个就涉及到JS引擎的实现了,像V8引擎就对其进行了优化,对于闭包内层函数没有使用到的自由变量,是不会被保存的,这样就大大提升了内存的使用率;

JavaScript闭包的那些事的更多相关文章

  1. JavaScript闭包的底层运行机制

    转自:http://blog.leapoahead.com/2015/09/15/js-closure/ 我研究JavaScript闭包(closure)已经有一段时间了.我之前只是学会了如何使用它们 ...

  2. 我从来不理解JavaScript闭包,直到有人这样向我解释它...

    摘要: 理解JS闭包. 原文:我从来不理解JavaScript闭包,直到有人这样向我解释它... 作者:前端小智 Fundebug经授权转载,版权归原作者所有. 正如标题所述,JavaScript闭包 ...

  3. 我从来不理解 JavaScript 闭包,直到有人这样向我解释它...

    正如标题所述,JavaScript 闭包对我来说一直有点神秘,看过很多闭包的文章,在工作使用过闭包,有时甚至在项目中使用闭包,但我确实是这是在使用闭包的知识. 最近看国外的一些文章,终于,有人用于一种 ...

  4. 那些年,我们误解的 JavaScript 闭包

    说到闭包,大部分的初始者,都是谈虎色变的.最近对闭包,有了自己的理解,就感觉.其实我们误解闭包.也被网上各种说的闭包的解释给搞迷糊. 一句话:要想理解一个东西还是看权威的东西. 下面我来通俗的讲解一个 ...

  5. [JavaScript闭包]Javascript闭包的判别,作用和示例

    闭包是JavaScript最重要的特性之一,也是全栈/前端/JS面试的考点. 那闭包究竟该如何理解呢? 如果不爱看文字,喜欢看视频.那本文配套讲解视频已发送到B站上供大家参考学习. 如果觉得有所收获, ...

  6. 《Web 前端面试指南》1、JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

  7. JavaScript 闭包深入浅出

    闭包是什么? 闭包是内部函数可以访问外部函数的变量.它可以访问三个作用域:首先可以访问自己的作用域(也就是定义在大括号内的变量),它也能访问外部函数的变量,和它能访问全局变量. 内部函数不仅可以访问外 ...

  8. JavaScript闭包(Closure)

    JavaScript闭包(Closure) 本文收集了多本书里对JavaScript闭包(Closure)的解释,或许会对理解闭包有一定帮助. <你不知道的JavsScript> Java ...

  9. Javascript闭包和C#匿名函数对比分析

    C#中引入匿名函数,多少都是受到Javascript的闭包语法和面向函数编程语言的影响.人们发现,在表达式中直接编写函数代码是一种普遍存在的需求,这种语法将比那种必须在某个特定地方定义函数的方式灵活和 ...

随机推荐

  1. python xlwt写Excel表

    1 xlwt第三方库 说明:xlwt是一个用于将数据和格式化信息写入并生成Excel文件的库. 注意:xlwt不支持写xlsx表,打开表文件报错. 官方文档:https://xlwt.readthed ...

  2. eclipse的安装及最大子数组求和

    我安装的是eclipse.由于eclipse是一个基于Java的课扩展开发平台,所以在安装eclipse之前要先安装Java的开发工具JDK(Java Devolopment Dit),且安装JDK需 ...

  3. monkey怎么做APP自动化?

    前言: monkey是andriod平台自动化测试的一种手段,通过monkey程序模拟触摸屏幕.滑动,滚屏,按键来对设备进行压力测试,检测程序多久会出现异常第一种:设置好命令,做随机自动化 什么时候可 ...

  4. python的作用域、globals()-全局变量 和 locals()-局部变量

    在python中,函数会创建一个自己的作用域,也称为为命名空间.当我们在函数内部访问某个变量时,函数会优先在自己的命名空间中寻找. 我们自己定义的全局变量均在python内建的globals()函数中 ...

  5. PyCharm - 关联mysql失败 - Server returns invalid timezone. Go to 'Advanced' tab and set 'serverTimezone' property manually.

    时区错误,MySQL默认的时区是UTC时区,比北京时间晚8个小时. 所以要修改mysql的时长 在mysql的命令模式下,输入: set global time_zone='+8:00'; 再次连接成 ...

  6. vue2.0多页面开发

    我们平常用vue开发的时候总觉得vue好像就是专门为了单页面应用而诞生的,其实不是.因为vue在工程化开发的时候很依赖webpack,而webpack是将所有的资源整合到一块,弄成一个单页面.但是vu ...

  7. SYCOJ411

    题面描述 MasMas在面试某大厂时遇到了一道有趣的题.面试官要求MasMas写一个程序找出几个数中,出现次数为奇数的那个数.MasMas抓耳挠腮,请你帮帮他. 输入描述 第一行输入一个数nn (1 ...

  8. 关于CKCsec安全研究院

    关于CKCsec安全研究院 CKCsec安全研究院所有文档开源于语雀,会源源不断更新. 部分内容 微信公众号 知识星球 使用需知 由于传播.利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均 ...

  9. json 转换C# class(用于对接api

    //说明//使用场景:对接api,返回json结果,直接转换C# class//如何使用:复制下面js代码在浏览器控制台执行 ` "order_item_id": "28 ...

  10. MySQL之MVCC与幻读

    转自 https://blog.csdn.net/qq_31930499/article/details/110393988 如果是快照度,直接采用MVCC,如果是当前读,才会走next-key lo ...