• javascript 作用域链


      最近想整理一下js执行代码的一些知识,如果有出错的地方还请指正。

      执行环境(Execution Context)

      所有的javascript代码都是在一个执行环境中被执行的。它只是一种机制,用来完成运行时作用域、生存期等方面的处理。

      代码分为三种类型:

    1.   Global Code 
    2.   Eval Code
    3.   Function Code 

      这是一个EC结构

    可以理解如下:

    activeExecutionContext = {
        VO: {...}, // or AO
        this: thisValue,
        Scope: [ // Scope chain
          // 所有变量对象的列表
          // for identifiers lookup
        ]
    };

      当一段程序开始时,会先进入到全局执行上下文环境。此时如果调用了某些函数,就会进入他们的上下文环境,执行完之后再退出该上下文环境。

    假设激活了一个EC1的上下文,图解如下

      变量对象(VO)和活动对象(AO)

      变量对象是一个与上下文相关的数据作用域,用于存储被定义在上下文中的变量和函数声明(不包括函数表达式)。在global全局上下文中,变量对象即使全局对象自身。

    copy一个例子

    var foo = 10;
    
    function bar() {} // // 函数声明
    (function baz() {}); // 函数表达式
    
    console.log(
      this.foo == foo, // true
      window.bar == bar // true
    );
    
    console.log(baz); // 引用错误,baz没有被定义

    全局上下文中的变量对象(VO)会有如下属性:

    在一个函数的上下文中,变量对象被表示为活动对象

    活动对象在进入上下文中初始化成了

    AO = {
      arguments: <ArgO>
    };

    arguments属性值就是Arguments对象

    之后就是存储当前上下文的变量与函数声明

      作用域链

      作用域链是一个 对象列表,用于检索上下文中出现的 标识符(变量名称、函数声明,普通参数)。

      在函数创建的时候,当前的作用域链被存储到了函数的[[scope]]属性中。当进入上下文创建AO/VO之后,上下文(EC)的Scope属性作了如下处理:

    Scope = AO|VO + [[Scope]]

      知识点就是这些,现在我们来一段代码,把过程给串联起来。在这之前,我们要知道另一个知识,就是执行上下文的代码被分成两个基本的阶段来处理

    1. 进入执行上下文
    2. 执行代码
    var x = 10;
     
    function foo(m) {
      var y = 20;
     
      function bar() {
        var z = 30;
        alert(x +  y + z + m);
      }
     
      bar();
    }
     
    foo(10); // 60

    首先先进入全局上下文环境

      全局上下文的变量对象(代码执行时,x才被赋值)是:

    globalContext.VO === Global = {
      x: 10
      foo: <reference to function>
    };

    globalContext.Scope = globalContext.VO;

    当"foo"创建时,

    foo.[[Scope]] = [
      globalContext.Scope
    ];
    //也就是
    foo.[[Scope]] = [
      globalContext.VO
    ];

    在"foo"激活时,进入了foo的上下文,foo上下文中的活动对象(代码执行时,y才被赋值)是:

    fooContext.AO = {
     arguments:<Arg>,
     m: 10,
     y:
    20, bar: <reference to function> };

    此时foo上下文中的作用域链为:

    fooContext.Scope = fooContext.AO + foo.[[Scope]] // i.e.:
     
    fooContext.Scope = [
      fooContext.AO,
      globalContext.VO
    ];

    函数"bar"创建时,其[[scope]]为:

    bar.[[Scope]] = fooContext.Scope;
    //也就是
    bar.[[Scope]] = [ fooContext.AO, globalContext.VO ];

    同理,bar就不写了。最后bar上下文的作用域链为:

    barContext.Scope = barContext.AO + bar.[[Scope]] 
     
    barContext.Scope = [
      barContext.AO,
      fooContext.AO,
      globalContext.VO
    ];

    在alert执行时,作用域链中查找标示符如下

    查找 x,  barContext.AO 无 ----> fooContext.AO 无 ---->globalContext.VO 有

    查找 Z,  barContext.AO 有

    其他的就自己看了。

      局部变量、全局变量

      在作用域链中查找标识符是需要花时间的,所以就明白为什么需要尽量使用局部变量(将频繁使用的全局变量缓存下来),全局变量尽量少用了。并且with会破坏作用域链,它会将指定的VO/AO加入到作用域链的顶端,这样在标识符查找时,需要查找更长的作用域链。

      闭包

      理解了上面的概念,闭包就很容易理解了。闭包是代码块和创建该代码块的上下文中数据的结合。

      

  • 相关阅读:
    需要学习的技术
    面试资料
    数据库设计三大范式
    java List 排序 Collections.sort() 对 List 排序
    hibernate的延迟加载
    索引失效原因总结
    mybatis调用oracle存储过程
    Android开发中需要注意哪些坑
    Intent在Activity之间传值的几种方式
    Android动画(Animations)
  • 原文地址:https://www.cnblogs.com/ximuncu/p/4697089.html
Copyright © 2020-2023  润新知