分享

javascript执行上下文、作用域与闭包(第六篇)

 F2967527 2020-11-23

终于讲到闭包了,当你在百度上搜索闭包时,你会被搜索出来的结果吓一跳,我的天,为什么说得都不一样?直到把所有的解释都看过了,我就只想说一句,到底谁说的是对的…

在这么多的不同解释里,我认真思考了很久,到底该相信谁?最后我选择相信大道至简,因为我始终觉得理论来源于实践,而实践一定不是在象牙塔里,而是可以摸得到的简单的东西。

下面就来讲最原始的闭包的示例:

function fn(){var max=10;return function bar(x){if(x>max){alert(x);}}}var f1=fn();f1(15);//15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

如上代码,bar函数作为返回值,赋值给f1变量。执行f1(15)时,就相当于执行bar(15),沿着作用域链取到fn作用域下的max变量的值。

这已经产生了一个最基本的闭包,用自然语言描述的话就是:

(1)定义普通函数 A

(2)在 A 中定义普通函数 B

(3)在 A 中返回 B

(4)执行 A, 并把 A 的返回结果赋值给变量 C

(5)执行 C

而它的形式就是用“return”作为桥梁,链接A外的变量C和A内的变量B。

用一个归纳的话说就是:

当一个内部函数被其外部函数之外的变量引用时,就形成了一个闭包

如果你认为这就是闭包的全部,那你就有些狭隘了。

下面一个例子,就展示了这个狭隘之处:

function A(){    var count=0;    function B(){        count++;        alert(count);    }    return B;} var c=A(); c();//1 c();//2 c();//3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

那么你就会惊奇于闭包还有如此神秘的一面,其实对于这一面,网上有很多解释,解释得五花八门,但大都没解释到本质上。

其实这一系列的文章的标题都是“javascript执行上下文、作用域与闭包”,说明闭包与执行上下文,作用域是有紧密联系的,下面就来演示一下闭包那神秘的一面的本质。

咱们可以拿本文的第一段代码(稍作修改)来分析一下。

function fn(){var max=10;return function bar(x){if(x>max){alert(x);}}}var f1=fn(),max=100;f1(15);//15
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

我们来一步一步揭开那神秘的面纱:

在这之前,告诉大家一个很重要的东西—-我们在前面讲执行上下文栈时当一个函数被调用完成之后,其执行上下文将被被弹出,即销毁,其中的变量也会被同时销毁。

但我们要铭记:在闭包里,函数调用完成之后,其执行上下文环境不会立即被销毁。(这句话是不正确的,但这里读者可以先用这个错误的解释去看下面的步骤,先对闭包形成的原因有一个大致的了解,最终正确的理解在 厚积薄发—从此再也不用担心闭包问题

第一步,代码执行前生成全局上下文环境,并在执行时对其中的变量进行赋值。此时全局上下文环境是活动状态:
这里写图片描述

第二步,执行第17行代码时,调用fn(),产生fn()执行上下文环境,压栈,并设置为活动状态。
这里写图片描述

第三步,执行完第17行,fn()调用完成。按理说应该销毁掉fn()的执行上下文环境,但是因为执行fn()时,返回的是一个函数。函数的特别之处在于可以创建一个独立的执行上下文,当代码执行到return时,按理说应该把fn()的执行上下文销毁,但是由于return的bar()存在对fn()上下文的引用,(因为存在对变量max的需要)所以不能将其销毁,这里就要提到js的垃圾回收机制,当一个函数不存在外部对它的引用的时候,就自动将其销毁。这里存在对把不能把fn()上下文销毁。

第四步,执行到第18行时,全局上下文环境将变为活动状态,但是fn()上下文环境依然会在执行上下文栈中。另外,执行完第18行,全局上下文环境中的max被赋值为100。如下图:
这里写图片描述

第五步,执行到第20行,执行f1(15),即执行bar(15),创建bar(15)上下文环境,并将其设置为活动状态
这里写图片描述

执行bar(15)时,max是自由变量,需要向创建bar函数的作用域中查找,找到了max的值为10,

这里的重点就在于,创建bar函数是在执行fn()时创建的。fn()早就执行结束了,但是fn()执行上下文环境还存在与栈中,因此bar(15)时,max可以查找到。如果fn()上下文环境销毁了,那么max就找不到了。

可以看到,使用闭包会使变量保存在内存中,但是缺点就是会增加内存开销。

在网上有些资料解释闭包的主要用途有两个,一个是可以使外部变量访问到一个函数的内部变量,一个是使变量保存在内存中。其实这句话不太贴切,正确的说法应该是:使外部变量访问到一个函数的内部变量是闭包的形式,使变量保存在内存中是闭包的工作原理。

到这里,如果觉得有种恍然大悟的感觉,不妨试一试下面的例子

function A(){    var count=0;    function B(){        count++;        alert(count);    }    return B;} var c=A(); c();//1 c();//2 c();//3
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

大家可以试一试解释为什么三次调用c(),结果分别是1,2,3呢?

如果还有些不懂,我会在下一篇里详细讲一下我的理解。


本文参考了王福朋老师的深入理解javascript原型和闭包(15)——闭包

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多