zoukankan      html  css  js  c++  java
  • 我的JavaScript之旅——“闭包”是什么时候创建的

    function Outer(){
    var x = 1;
    function Inner(y) {return x + y};
    return Inner;
    }

    对于这样一个简单的闭包函数,下面两种调用方式有什么不一样的地方?

    //方式1
    var inner1 = Outer();
    var result = inner1(2); //3
    //方式2
    var result = Outer()(2); //3

    此篇试图解答这个问题。先复习一下:

    上篇文章说到,每次执行一个function时,就会进入一个新的“执行上下文”(execution context)。context的几个重要属性:

      1, 有一个对应的variable object;在global context中就是global object。
      2, 有一个对应的scope chain,这个scope chain的第一个object就是variable object;
      3, 有一个不变的this变量。

    其中第2条,scope chain是JS实现闭包(closure)的关键所在,这篇对“闭包”展开描述,以加深印象。后续文章将对this专门探讨。

    Variable object的实例化三部曲

    我们已经知道,当JS执行时碰到一个变量,它会到scope chain里递归去找,而scope chain是由variable object和global object组成的一个object chain,global object包含JS预定义好的所有object,function的variable object则会包含函数内声明的所有东西,包括function的参数、内部函数、局部变量。创建Variable object的过程有三步,上篇有写过,这边是官方的文档。简述如下:

      1, 为variable object创建与函数参数同名的属性;属性值为传入的参数值。

      2, 对于“function declaration(见下节)”,首先创建函数,然后为variable object创建属性;属性值即为该函数实例。覆盖1的同名属性。

      3, 为variable object创建各变量的属性;属性值为undefined。不覆盖1,2的同名属性。

    function declaration vs. function statement

    下面就是一个function declaration:  

    function A(){
    }

    下面是一个function statement:

    var A = function A(){
    }

    这是创建函数的三种方式之二,区别在上面的三部曲中就可以看出来:

      1, 在进入execution context时(还没执行任何代码),function declaration就已经在第2步创建函数实例起来了;而function statement属于第3步,而且初始值是undefined,要到执行这行代码时,函数才会被创建起来。

      2, function statement不会覆盖同名的function declaration。

    验证一下:

      

      如图:A可以先用再声明;B则不行。B在执行到最后一句之前,都是undefined。但是B这个属性是一开始就在的:

      

      (this是什么?因为上面的代码是执行在global context中,B属性应该创建到global object身上,也就是this。下篇会详述this。)

    三部曲中说了,function declaration会覆盖同名的1里的参数,所以它是varaible object里的第一等公民:

      

    而function statement不会。

      

    闭包的创建时机

    对于开头的这段代码:

    function Outer(){
    var x = 1;
    function Inner(y) {return x + y};
    return Inner;
    }

    然后执行 

    var inner1 = Outer();

    回忆一下这时会发生什么?

    会进入一个新的“执行上下文”(Outer Context),创建OuterVariableObject(有x和Inner属性),放到OuterScopeChain的最前方。而且会创建Inner函数,创建时把当前Scope Chain作为Inner函数的[[Scope]]属性。这是重点。

    创建起来的Inner函数被inner1变量引用,Inner有[[Scope]]属性,引用了OuterScopeChain,即[OuterVariableObject, global object],而OuterVariableObject又引用了局部变量x。所以inner1变量就对Outer函数体内的局部变量x有间接的引用。内部函数对外部函数的变量有了引用关系——闭包就是这时产生的。每次对外部函数的调用,都会产生一次闭包。


    garbage collection

    很多人都听过闭包容易引起内存泄露。为什么呢?因为如上所述,inner1变量对x有间接引用,而inner1是声明在global context下的一个变量,它在global context下随时可以被用,那么JS的垃圾回收器就不会回收它(inner1),当然也就不会回收它所引用的x——直到退出global context,也就是我们关掉网页的时候。

    这就是文章开头两种调用方式的区别:方式1,x在关掉网页前一直不能被回收;而方式2,x会被回收。

    后续文章将介绍闭包的用处,和this关键字(比我想象的复杂)。


  • 相关阅读:
    SpringBoot 集成Hystrix熔断
    windows10 个性化启动Python,cmd窗口显示启动名称
    Web前端 table去掉td边框大小及颜色
    Windows 10 运行.bat文件启动Jar项目
    SpringBoot Feign接口方式调用服务
    SpringBoot Ribbon负载均衡策略配置
    SpringBoot Eureka集群配置
    SpringBoot集成Eureka
    面试题 16.01. 交换数字
    1476. 子矩形查询
  • 原文地址:https://www.cnblogs.com/CaiAbin/p/1826287.html
Copyright © 2011-2022 走看看