您好、欢迎来到现金彩票网!
当前位置:秒速快三计划 > 属性闭包 >

JavaScript 里的闭包是什么?应用场景有哪些?

发布时间:2019-07-03 04:27 来源:未知 编辑:admin

  闭包(Closure)这个词的意思是封闭,将外部作用域中的局部变量封闭起来的函数对象称为闭包。被封闭起来的变量与封闭它的函数对象有相同的生命周期。

  C 语言中,可以获取一个函数的指针,并通过指针间接调用此函数。这就是 C 语言中的对象(函数对象也是对象)。但 C 语言中的函数对象不是闭包——它不能访问外部作用域的局部变量。

  Javascript 中,每个函数都有一个与之相关联的作用域链。每次调用 JavaScript 函数的时候,都会为之创建一个新的对象用来保存局部变量,并把这个对象添加至作用域链中。当函数返回时,再将这个对象删除,此对象会被当做垃圾回收。但如果这个函数定义了嵌套的函数,并将它存储在某处的属性里,就意味着有了一个外部引用指向这个嵌套的函数。它就不会被当作垃圾回收,它所指向的变量绑定对象同样不会被回收。

  由此可见,JavaScript 中的函数对象是闭包——可以把外部作用域的局部变量“封闭”起来。

  面向对象中的“对象”是指问题空间中的元素(猫、狗)及其在解空间中的表示(new Cat(); new Dog())。对象是过程(函数)与数据的结合。

  对象是在数据中以方法的形式内含了过程,闭包是在过程中以环境的形式内含了数据。所谓“闭包是穷人的对象”、“对象是穷人的闭包”,就是说使用其中的一种方式,就能实现另一种方式能够实现的功能。

  个人总结,内容根据《代码的未来》、“犀牛书”、以及《Thinking in Java》整理。

  要真正理解JavaScript里的闭包,就一定要脱离能看得见的js代码,看看在js引擎里,一个函数的创建和执行到底发生了什么,每一个变量名又是如何解析的。

  首先,每一个函数都有一个隐藏的属性[[Scope]],这个属性保存着函数的作用域链,它本质上是一个集合,它的每个值引用一个数据的集合。函数中的变量名解析,实质上就是在[[Scope]]引用的每一个数据的集合里查找同名的键,如果找到了,就读取这个键对应的值,如果没有,就触发异常。

  ,则[[Scope]]属性的第一个值会引用当前的全局上下文,所有全局变量及其存储的数据都在这个集合里。此时[[Scope]]属性如下:

  会创建一个Execute context,即运行时上下文,它存储调用该函数的对象,函数的参数数组,函数的局部变量(值还未定义),该集合会被压入[[Scope]]属性,此时[[Scope]]属性如下:

  此时运行时上下文没有任何引用了,符合了被销毁的条件,在将来的某个时刻会被垃圾收集器销毁。此时[[Scope]]属性如下:

  当一个子函数被创建时,显然,这个创建是父函数的执行导致的,所以当子函数创建时,父函数已经处于执行阶段,所以父函数的执行上下文已经创建了。

  同时,因为子函数也在父函数的局部变量作用域内,所以,子函数在创建的时候,除了要引用全局上下文,也需要引用父函数的执行上下文,否则子函数就无法访问父函数的局部变量,违背了设计的要求。

  (Execute context),//这里引用的是父函数的执行上下文,自身的执行上下文此时还未创建。

  当一个子函数执行时,因为它同样是函数,所以它同样需要创建自己的执行上下文,

  对自身执行上下文的引用,对父函数的执行上下文的引用并没有解除,这意味着,父函数的执行上下文与子函数本身共存亡了。

  所以,为什么父函数的局部变量没有被销毁?因为它们所在的执行上下文还被子函数的[[Scope]]属性引用着,只要子函数还存在引用,垃圾收集器就不会销毁它们所在的执行上下文。

  注意,因为父函数已经执行完毕了,所以父函数的执行上下文中的局部变量如果有赋值,那此时这些变量也已经有了相应的值。

  另外,因为父函数的局部变量并不在全局上下文中,所以它只能在子函数的变量解析中被访问,自然而然就相当于它们是子函数私有的了。

  这里,Counter 函数返回两个闭包,函数 increment 和函数 get。 这两个函数都维持着 对外部作用域 Counter 的引用,因此总可以访问此作用域内定义的变量 count.

  闭包本质还是函数,只不过这个函数绑定了上下文环境(函数内部引用的所有变量)。

  在函数式编程中,闭包的逻辑就是:『让程序运行环境来管理状态』。命令式语言围绕状态来建模,每次指令操作都在控制状态的变化,需要人为管理;而闭包是对行为的控制,将运行逻辑和上下文绑定,执行闭包的时候,逻辑和上下文是关联的,一个十分简单的例子:

  上面,foo 是一个普通函数,但是当它被执行后,连同 foo 内部作用域以及返回的匿名函数,构成了闭包函数 bar,其中变量 v 将由这个闭包在运行时自行管理,每次执行都会触发匿名函数的逻辑,将 v 的值增 1。

  闭包的特点很鲜明,闭包内,变量无法释放,无法被直接访问;闭包可以被延迟执行。所以可以用它来做一些事情:

  将代码封装成一个闭包形式,等待时机成熟的时候再使用,比如实现柯里化和反柯里化

  利用闭包可以给对象设置私有属性并利用特权(Privileged)方法访问私有属性。

  JavaScript 中的闭包与其 Scope Chain 特性真是密不可分的.

  var closure = foo(); // 这个时候返回的是 bar() 这个函数外加其包上的变量 a;

  var closure2 = foo(); // 这里同样生成了另外一个闭包(实例)

  对于常规的 foo() 方法来说, 在其内部的变量 a 的存在应该在 foo() 方法执行完毕以后就消失了, 但是 foo() 方法返回了一个新的方法 bar(), 而这个方法却访问到了 foo() 方法的变量 a (JavaScript 通过 Scope Chain 访问到父级属性), 而方法 bar() 的存在延长了变量 a 的存在时间, 类似与将变量 a 关闭在了自己的作用域范围内一样, 只要方法 bar() 没有失效, 那么变量 a 则会一直伴随着方法 bar() 存在, 而变量 a 与方法 bar() 的这样存在形式被称为闭包;

  在我看来, 虽然时常听到闭包这个概念, 但真正的应用还真不是很多, 也没看到或者想到比较经典的应用. 在 JavaScript 中, 使用得最多的, 恐怕还是将 function 作为 first class 的情况使用, 就好比你可以 var a = new Number(0); 可以将 a 当作函数的参数不断的进行传递, 你也可以 var f = new Function(arg1, arg2...., functionBody) [new Function(x, y, return (x + y)/2)] 来定义函数, 然后将 f 作为参数在函数中不断的传递; 但我一般不会让这个 function 产生闭包来进行传递, 而是会传递一个独立的 function 避免写多了自己都忘记是哪个了.

  如开篇所说, JavaScript 中的闭包实现与 JavaScript 的 Scope Chain 是密不可分的. 首先在 JavaScript 的执行中会一直存在一个 Execute Context Stack (想想 JavaScript 解释器在看到一个 alert(x) 的时候, 如果没有上下文他怎么知道这个 x 是什么?), Execute Context Stack 中最下面一个一定是 GlobalContext, 而在每一个

  就会向这个 stack 中压入一个此 Function 的 Execution Context; 而一个 Execution Context 的组成分为三部分:

  1. Variable Object: 存储方法内的变量 vars, 方法传入的参数, 函数内定义的函数等等(函数表达式不保存), Variable Object 在任何时候是不可以被直接访问到的, 当然不同的 JS 引擎提供了访问接口就说不定了;

  2. Scope Chain: 这个函数执行的时候用以寻找值的 Scope Chain, 这个 Scope Chain 由 Variable Object + All Parent Scopes 组成, Variable Object 会放在这个 Scope Chain 的最前面, 这也是为什么函数内的变量会被最先找到;

  3. thisValue, 函数被调用的时候的 this 对象, 存储的就是函数的调用者(caller)的引用;

  对于 Variable Object 在不同的情况下会有不同的定义, 例如在全局的时候被称为 Global Object, 而在函数中则被称为 Activation Object 激活对象;

  正是由于有了 Execution Context 中的 Scope Chain, JavaScript 才能够使得在方法 bar()

  的内部访问到方法 foo() 中的变量 a, 才能够使方法 bar() 将变量 a 关闭在自己的作用范围内不让他随 foo() 方法的执行完毕而销毁;

  ascript/chapter-1-execution-contexts/

  闭包的正确称谓是 first-class function with lexical scope。

  First-class function 决定了函数可以在另一个函数内部被定义,并且作为 return value 返回。

  Lexcial scope 指:当一个名称不是参数也不是局部变量的时候,VM 会从该函数定义的函数运行时的局部变量中绑定。如果仍然没有再向上类推(最终所有函数都是在 global chunk 运行时被定义,所以最后都会从 global 变量中绑定)。

  Structure and Interpretation of Computer Programming 的第三章的 environment model 是对闭包最精确的描述。每个函数被定义时有一个 bound environment,每个函数每次被调用时有一个 created environment。一个函数定义时的 bound environment 是这个函数的外层函数被调用时的 created environment。局部变量在 created environment 中,闭包变量在 bound environment 中。

  闭包,这个概念对于每位JSer而言都不陌生,它几乎伴随着每个前端入门者的初学阶段,重要到几乎每家公司面试都会问。

  关于闭包究竟是什么,闭包干嘛用的,网上各种回答也是五花八门,动不动就扯到隐匿变量/内存泄漏这些概念,让没有C基础的初学者越看越晕,我不能说那些是错的,不过显然对新手不太友好。曾几何时我也是被那些个故作高深的概念绕得七荤八素云里雾里,那今天这篇文章以一个简单到80岁老太都看得懂的demo,来阐明闭包的本质及作用。

  看过红宝书的都见过这个词,不过Nicholas大神的例子我觉得还是有点晦涩,我来举个更简易的例子——现在有一个button和一个有数字的span,button负责让数字加1,代码如下:

  聪明的你想必已经看出来了,将声明变量a这个步骤从外面挪到onclick事件函数里面的话,不管你怎么按a将永远保持11,页面的span里的数字也将永远是11,为什么?你可能会说:因为每次按按钮的时候,我都重新声明了一个变量a啊,等于每次都重置为10了啊,每次++只能是11然后无限循环balabala。。。

  好,那我问你第二个问题:为什么声明a这一步写在函数外面就行了?你会说因为a一开始只声明了一次啊,每次++好的a都是在旧的a上面进行操作啊balabala。。。这个回答不能说错,但显然没有进行深入的思考,就像古时候人们都觉得苹果落地是天经地义的一样,只有保持怀疑,才有机会推导出万有引力定律。

  红宝书告诉我们,没有用的局部变量就会被销毁内存,一开始我百思不得其解(没办法,没有C基础嘛),后来通过这个点击的例子就闹明白了——在你每次按好按钮,函数执行完毕后,你的这个a就被当成没有用的变量被回收了,或者换句话说,内存被销毁了!所以你的a始终都是初始的那个10!而在例子1的js代码里,a是全局变量,全局变量只有当你关闭页面or浏览器的时候才会被销毁!所以a仿佛就有了存储功能,能够记录每次变化后的值,你点或不点,a就在那乖乖的,保持着上一次点击好++的那个值。

  好,我相信你看到这里应该明白回收变量(这个词在js里其实就等价于销毁内存)是什么个意思了,那这和我们今天主题“闭包”有什么关系?别急,看代码,我给你在例子1上做点小改动,你就看明白了!

  我在例子1的外面加了自执行匿名函数包裹了一下,现在你就可以把这段操作看成是函数包裹函数的形式了,放心,代码一样能跑!现在我们把外面的匿名函数成为father函数,把里面点击按钮的函数称为child函数,没毛病吧!毕竟包裹关系,好,现在我就可以说,这就是个闭包环境了,为什么?因为child函数引用了father函数的变量a啊!闭包的最大特性就是,如果里函数引用(or访问,这俩词在这个语境下是等价的)了外函数的某个变量,那这个变量就能享受和全局变量一样的特权,丫不会被回收!除非你关闭页面or浏览器!这也就是为什么a能一直++并正常显示的问题了,因为他被child函数一直访问着,不会被销毁!不会被销毁!不会被销毁!重要的事情说三遍!闭包就这个作用!

  好的,那顺着思路继续走,那内存泄漏是什么鬼?很简单,如果你像这种享受全局变量不会被销毁的特权的闭包变量多到一定数量了,那内存就要撑爆了,毕竟咱的电脑对待浏览器是很抠门的,内存就分配给你那么点,一多就会爆,这就是内存泄漏,并不是什么高大上的概念!

  相信看到这里你应该能够大致明白闭包,以及内存销毁的大致概念了吧~其实我对外面那些讲解闭包的文章最不满意的一点就是,各种外函数return里函数,这种写法会给新手们的理解制造很大的障碍,毕竟return来return去,要调用的时候还要再加一对(),实在反人类,并且最大的问题在于:看多了return的例子,新手们很可能会误认为只有外函数return里函数才是闭包!压根不是这样的!闭包跟return是既不充分也不必要的关系!(虽然实际业务中我们经常要return个里函数来达到强行闭包的效果),并且,闭包和自执行匿名函数也是既不充分也不必要的关系,也不要因为我这个例子就有这种想法,return里函数也好,自执行也好,都只是为了方便理解而已,你甚至可以把全局作用域当成一个最外层的自执行匿名函数的局部环境,就像例子3这样,或者在所有代码外面都包裹上window.onload = function (){}这样看的话,所有引用到最外层变量的函数,就都是闭包了!感谢阅读!

  当你将一个好书作为参数传递到另一个函数或者在一个函数内部return一个函数时,闭包就创建了

  但是要注意,因为闭包保留当前词法作用域的引用 而闭包是函数 函数又是对象

  因此在不使用闭包的时候要清除,避免内存泄漏。就好像使用了定时器就要记得清除定时器,同样的道理啦 :)

  你妈给你马铃薯,油锅或者给你成品薯条然后把你锁房间里,当然给你用电脑不断WIFI,你自己吃薯条自己活着。

http://isaegil.net/shuxingbibao/346.html
锟斤拷锟斤拷锟斤拷QQ微锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷微锟斤拷
关于我们|联系我们|版权声明|网站地图|
Copyright © 2002-2019 现金彩票 版权所有