zoukankan      html  css  js  c++  java
  • Scope Chain(作用域链)

    本章,我们讨论一下ECMAScript中的作用域链 , 开门见山。

    什么是作用域链

    i.ECMAScript是允许创建内部函数的,甚至能从父函数中返回这些函数。作用域链正是内部上下文中所有变量对象(及自由对象)的列表。此链用来变量解析查询。

    作用域链的特性

    i.是执行上下文的一个属性

    activeExecutionContext = {
          vo : {},  
          this : thisValue,
          scope : []  
    }

    ii.逻辑上是一个数组,每一个元素是一个变量对象

    iii.定义为:Scope = ActiveContext.VO + Function.[[Scope]]  ([[Scope]] 是函数的属性)

    理解作用域链

    i.[[Scope]] 是函数的私有属性,在函数被解析时创建,不会改变。

      为了让大家更好的理解,先让大家看一段代码:

    var x = 'test';
    
    function foo() {
        console.log(x);
    }
    
    (function() {
        var x = 'what';
    
        foo();
    })();

    上面的代码输出会是什么呢?为什么?

    控制台将会输出 'test',而非 'what' 。这个例子也说明,一个函数的[[Scope]] 持续存在,即使是在函数创建的作用域已经完成之后。

    ii.[[Scope]] “通常”(存在意外) 包含了父级函数的[[Scope]]属性,ECMAScript依靠这个特性来实现闭包。

    iii.[[Scope]]是函数的属性,这也意味着ECMAScript中没有Java那样的块级作用域。(ES6中对这一块得到了加强)只有函数级作用域。

        观察以下3种函数构造方式的差异:

    var x = 10;
    
    function foo() {
         var y = 20;
         
         // 函数声明方式创建
         function innerFoo() {
              console.log(x ,y);
         }
         
         //函数表达式方式创建
         var innerFoo2 = function() {
              console.log(x ,y);
         } 
         
         //构造函数方式创建
         var innerFoo3 = Function('console.log(x);console.log(y);'); 
    
        innerFoo(); //10 20
        innerFoo2(); //10 20
        innerFoo3(); //10 ,y not defined
    }

    通过以上代码,我们可以看出,通过Function构造函数创建的方法只拥有全局作用域

    iiii.变量的二维链式查找

       变量的解析是通过作用域链来实现的。

         变量本质是上以变量对象的属性方式存在,当变量对象与JavaScript中对象重叠时,它就会天然的受到原型链的影响。

    一段有趣的代码:

    function foo() {
        console.log(x);
    }
    
    Object.prototype.x = 10;
    
    foo(); //10

    原因: 此时,全局对象为 window(假设在浏览器中运行该段代码),而window对象是Object所派生的。根据原型链查找规则,实例中访问不到的属性和方法,将会在原型中查找。

    以下面的代码为例,其查找顺序是这样的:

    iiiii.全局代码和 eval 的作用域链

         全局代码中的作用域链仅包含全局对象

         eval的上下文与当前 calling context(调用上下文) 拥有相同的作用域链

         ES5中规定,如果对eval建立别名(非直接调用),这时作用域链仅包含全局对象

    iiiiii.作用域链是可以在运行时动态改变的

         在with 和 catch 语句中:Scope = withObject || catchObject + VO + [[Scope]]

         大多数情况下是不变的,但在with语句和catch语句块中,可以改变作用域链。这种技巧在有些时候非常有用,但大多数情况下,我们要尽可能避免

         ES5中,通过词法环境、词法环境记录的方式来扫描这种变化

    根据我们上面讲到的,大家看看如下代码:

    var x = 10,
          y = 10;
    
    with({x: 20}) {
         var x = 30 ,
               y = 30;
         console.log(x ,y); // 30 30
    }
    
    console.log(x ,y);  // 10 30

    输出的结果是: 30 30   10 30 ,为什么呢?实际上上面讲到的作用域链的动态改变时,已经对该问题做出了解答, 下面我们分析一下:

    在with和catch语块中的作用域链:

        1.x = 10 ,y = 10;

        2.对象{x:20}被添加到了作用域的前端

        3.在with内部,遇到了var声明。但是什么也没创建,因为在进入上下文时,所有变量已被解析添加

        4.在步骤2中,仅修改变量'x' ,实际上对象中的'x'现在被解析,并添加到作用域链的前端,'x'由 20 变为 30

        5.同样也有变量对象的属性'y'的修改,被解析后其值也由10变为30

        6.此外,在with声明完后,它的特定对象从作用域链中移除,(已改变的变量“x”--30也从那个对象中移除),即作用域链的结构恢复到with得到加强以前的状态。

        7.最后console中,当前变量对象 'x'保持相同,'y'的值 = 30(在with声明运行中已发生改变)

    iiiiiii.结合this一起

        直接调用函数,作用域为 withObject

    var x = 10; 
    
    with({
        foo : function() {
             console.log(this.x);
        } ,
        x : 20
        }) {
       foo(); // 20
    }    

        

    总结

    理解执行上下文、VO(变量对象)、this、作用域链是理解JavaScript执行的基础,尤其是this和作用域链

         

  • 相关阅读:
    Oracle数据库系统结构二(实例结构)
    Oracle数据库系统结构一(存储结构)
    SQL Plus的使用详解(登录和常用命令)
    Oracle的基本了解和配置
    Oracle11g的安装及删除
    C++编译预处理
    C++程序的多文件组织
    C++变量的存储类型
    C++动态存储方式与静态存储方式
    C++函数五(局部变量与全局变量和域运算符)
  • 原文地址:https://www.cnblogs.com/inJS/p/4882663.html
Copyright © 2011-2022 走看看