zoukankan      html  css  js  c++  java
  • 我的JavaScript之旅——从Scope Chain到Closure

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

    执行上面这段代码的过程中,有哪些事情发生?Inner函数为什么可以引用Outer函数的参数x?closure是怎么实现的?本文试图回答这些问题。

    术语

    本文虽然所讲理论并不复杂,但用到不少名词,初读时相对比较晦涩,下面列出术语和简短解释,便于阅读时随时查看。

    • global:engine预先创建好的一个object,里面有所有built-in objects的属性。
    • globalContext:本文术语,用作表示全局的execution context。
    • globalScopeChain:本文术语,用作表示全局的execution context所拥有的Scope Chain,里面只有一个对象为global,用代码表示为 [global]
    • functionContext:本文术语,用作表示执行函数代码时,进入的新的execution context。
    • VariableObject:ECMAScript术语,在globalContext中即为global,在functionContext中是被创建的一个对象。在进入context时,被放到scope chain的最前方。
    • outerVariable:本文术语,表示进入OuterFunctionContext时被创建的Variable Object。
    • innerVariable:本文术语,表示进入InnerFunctionContext时被创建的Variable Object。
    • outerFunctionContext:本文术语,用作表示执行Outer这个函数时,进入的execution context。
    • outerScopeChain:本文术语,用作表示outerFunctionContext所拥有的Scope Chain。可用[outerVariable, global]表示。
    • innerFunctionContext:本文术语,用作表示执行Inner这个函数时,进入的execution context。
    • innerScopeChain:本文术语,用作表示innerFunctionContext所拥有的Scope Chain。可用[innerVariable, outerVariable, global]表示。

    JS代码种类

    JS代码分三种:

    1. Global code,全局代码
    2. Functioncode,函数内的代码。
    3. Eval code,为简单计,不在本文说明。

    Execution context

    任何一句JS代码,都是执行在一个特定的“execution context”下面。

    执行Global code时,JavaScript engine将会创建一个全局的context,为表述简单,我们把它叫做globalContext

    而每次进入Functioncode时,将会创建一个新的context,在函数返回(或有未捕获的异常发生)时,退出这个新的context,本文把它叫做functionContext

    a = 1; //进入globalContext

    function Outer(x){
    function Inner(y){return x + y;}
    return Inner
    }
    //在globalContext中创建Outer这个Function

    var inner = Outer(1); //执行Outer函数时进入新创建的outerFunctionContext上下文。
                 //然后退出,回到globalContext,把Outer(1)的返回值赋给inner这个变量。
    inner(2); //进入InnerContext,执行Inner函数的return x + y,然后退出,回到globalContext

    Scope Chain

    每个execution context都有一个关联的Scope Chain。所谓Scope Chain,其实就是一个List,里面有若干个object。

    global

    globalContext所关联的Scope Chain,这里不妨称之为globalScopeChain,这个chain里面只有一个object,就是global,global是一个engine事先创建好的对象,所有的built-in Object(比如Function()、Object()、Math)都会作为这个global对象的属性。

    Function型对象的[[Scope]]属性

    第一篇创建Function型对象的步骤里,第5步说了,会为这个Function型对象创建一个[[Scope]]属性,不过当初没有提到,这个属性的值是当前context的Scope Chain。

    Outer函数是在globalContext下创建起来的,因此Outer.[[Scope]] = globalScopeChain,也就是[global]。而Inner函数是在执行Outer函数时,也就是在outerFunctionContext下创建起来的,因此Inner.[[Scope]] = OuterContext的ScopeChain,是什么呢,往下看。

    Entering execution context

    每次进入一个context(不管是globaContext还是functionContext)时,都会有一系列的事情发生。

    1. 上面说到,每个context都有一个关联的Scope Chain,这个Scope Chain就是在此时会被创建起来的。
    2. 确定或创建一个Variable Object(ECMAScript术语),并把它放到Scope Chain的最前面。
      对于globalContext,这个Variable Object就是global,被放到globalScopeChain里(也是globalScopeChain里唯一的一个对象);
      而如果进入到一个functionContext,则会创建一个Variable Object起来,也放到Scope Chain的最前面,并且还会额外再做一件事——就是把当前Function的[[Scope]]里所有object,放到Scope Chain里面。因此执行Outer函数时,Scope Chain是这样的:[outerVariable, global];上面知道,创建Inner函数时,这个Chain将作为Inner函数的[[Scope]]属性,因此进入Inner函数的执行时,它的Scope Chain就是[innerVariable, outerScopeChain],也就是[innerVariable, outerVariable, global]。
    3. 实例化Variable Object,就是为Variable Object创建一些属性。
      首先,如果是functionContext,则把函数的参数作为Variable Object的属性;
      其次,把声明的函数作为Variable Object的属性;这里的属性将覆盖上面的同名属性。
      再次,把声明的变量作为Variable Object的属性,属性的初始值均为undefined,只有在执行赋值语句后,才会有值。这边的属性不会覆盖上面的同名属性。
    4. 为当前context确定this,this在context中是不变的。
      详细见下面的注解。
    //在执行一切代码之前,进入globalContext,global对象也已经创建好。

    //1.然后创建Scope Chain
    globalContext.ScopeChain = [];

    //2.确定variable object为global,并加入到scope chain中
    variable = global;
    globalContext.ScopeChain.push(global);

    //3.实例化variable object,创建a、Outer和inner三个属性,分如下步骤

    //3.1 Outer是“函数声明”,因此此时就会被创建起来。
    Outer = new Function('', '' [global]) //创建Outer函数,传入当前的scope chain,即[global]
    Outer.[[Scope]] = [];
    Outer.[[Scope]].push(global);
    //为Outer.[[Scope]]赋值
    variable.Outer = Outer //把创建起来的函数作为variable的属性。
    //
    3.2 对于变量a和inner,也创建为variable的属性,不过初始值为null
    variable.a = null;
    variable.inner
    = null;

    //4.确定this,在globalContext中为global。
    this = global;

    //以上是进入globalContext时所做的事情

    //以下开始执行代码。
      a = 1; //variable.a 此时才被赋值。
      function Outer(x){
      
    function Inner(y){return x + y;}
      
    return Inner
      }
      
    var inner = Outer(1); //这段代码用伪代码表示如下:
    //
    Outer(1),会执行Outer函数,因此进入新创建的outerFunctionContext上下文
    //
    1.创建ouerFunctionContext的Scope Chain,并放入Outer函数的[[Scope]]里所有的object
    outerFunctionContext.ScopeChain = [];
    outerFunctionContext.ScopeChain.push(global)
    //global是[[Scope]]里唯一的对象。
    //
    2.创建Variable Object属性,并放到Scope Chain的最前方。
    outerVariable= {arguments: xxx} //创建的variable有arguments等属性
    outerFunctionContext.ScopeChain.push(variable)
    //3.实例化variable object
    outerVariable.x = 1
    outerVariable.Inner
    = new Function('y', 'return x + y', [outerVariable, global])
    //注意上句创建Inner函数时,会传入当前的Scope Chain,即[outerVariable, global]
    //4.确定Outer函数体内的this参数,就是Outer函数对象。

    //最后回到globalContext中,把新建的Inner函数对象,返回给inner变量。

      inner(
    2); //最后执行的这句代码,将创建并进入InnerContext。

    初步结论

    现在已经知道,执行Outer函数时,对应的outerScopeChain的图如下,注意global对象忽略了指向所有built-in object的属性:

      

    执行Inner函数时,对应的innerScopeChain的图如下:

      

    Scope Chain的作用

    Scope chain的图出来了,那么它用来干嘛呢?执行inner函数的return x + y,会发现,我们需要两个变量,x和y。那么JavaScript将循着Scope Chain来查找,与__proto__链配合,也就是首先在innerVariable(以及其__proto__链)找x,没找到,则到outerVariable中找x,找到为1。 找y时类似。这就是Inner函数体中,可以访问得到Outer函数中定义的参数x的原理所在,不难想象,如果Outer函数中定义了局部变量z,那么z也会出现在outerVariable对象中,因此同样可以被Inner函数访问。内部函数可以引用外部函数的参数以及变量,这就是JavaScript传说中的闭包(Closure)

    下篇将对closure进一步阐述,待续。

  • 相关阅读:
    FPM
    Docker记录
    阿里云ECS发送企业邮件
    git操作
    vscode+vagrant+xdebug调试
    Spring Security开发安全的REST服务
    559. Maximum Depth of N-ary Tree
    《算法图解》之散列表
    766. Toeplitz Matrix
    893. Groups of Special-Equivalent Strings
  • 原文地址:https://www.cnblogs.com/CaiAbin/p/1818493.html
Copyright © 2011-2022 走看看