zoukankan      html  css  js  c++  java
  • Js 作用域与作用域链与执行上下文不得不说的故事 ⁄(⁄ ⁄•⁄ω⁄•⁄ ⁄)⁄

    最近在研究Js,发现自己对作用域,作用域链,活动对象这几个概念,理解得不是很清楚,所以拜读了@田小计划大神的博客与其他文章,受益匪浅,写这篇随笔算是自己的读书笔记吧~。

    作用域

    首先明确一个概念,js只有函数作用域(function-based),没有块级作用域,也就是只有函数会有自己的作用域,其他都没有。

    接着,作用域分为全局作用域与局部作用域。

    全局作用域中的对象可以在代码的任何地方访问,一般来说,下面情况的对象会在全局作用域中:

    • 最外层函数和在最外层函数外面定义的变量
    • 没有通过关键字"var"声明的变量
    • 浏览器中,window对象的属性

    局部作用域又被称为函数作用域(Function scope),所有的变量和函数只能在作用域内部使用。

    复制代码
    var foo = 1;
    window.bar = 2;
    
    function baz(){
        a = 3;
        var b = 4;
    }
    // Global scope: foo, bar, baz, a 
    // Local scope: b

    在创建这个函数的时候,这个函数的作用域与作用域链(函数的作用域链将会在运行时用到)就已经决定了,而是不是在调用的时候,这句话至管重要。

    作用域链

    每一个Javascript函数都被表示为对象,它是一个函数实例。它包含我们编程定义的可访问属性,和一系列不能被程序访问,仅供Javascript引擎使用的内部属性,其中一个内部属性是[[Scope]],由ECMA-262标准第三版定义。

      内部[[Scope]]属性包含一个函数被创建的作用域中对象的集合。此集合被称为函数的作用域链,它决定哪些数据可以由函数访问。此函数中作用域链中每个对象被称为一个可变对象,以“键值对”表示。当一个函数创建以后,它的作用域链被填充以这些对象,它们代表创建此函数的环境中可访问的数据:

    1 function add(num1, num2){ 
    2   var sum = num1 + num2; 
    3   return sum;
    4 }

      当add()函数创建以后,它的作用域链中填入了一个单独可变对象,此全局对象代表了所有全局范围定义的变量。此全局对象包含诸如窗口、浏览器和文档之类的访问接口。如下图所示:(add()函数的作用域链,注意这里只画出全局变量中很少的一部分)

      add函数的作用域链将会在运行时用到,假设运行了如下代码:

    1 var total = add(5,10);

      运行此add函数时会建立一个内部对象,称作“运行期上下文”(execution context),一个运行期上下文定义了一个函数运行时的环境。且对于单独的每次运行而言,每个运行期上下文都是独立的,多次调用就会产生多此创建。而当函数执行完毕,运行期上下文被销毁。

      一个运行期上下文有自己的作用域链,用于解析标识符。当运行期上下文被创建的时,它的作用域被初始化,连同运行函数的作用域链[[Scope]]属性所包含的对象。这些值按照它们出现在函数中的顺序,被复制到运行期上下文的作用域链中。这项工作一旦执行完毕,一个被称作“激活对象”的新对象就创建好了。此激活对象作为函数执行期一个可变对象,包含了访问所有局部变量,命名参数,参数集合和this的接口。然后,此对象被推入到作用域链的最前端。当作用域链被销毁时,激活对象也一同被销毁。如下所示:(运行add()时的作用域链)

      在函数运行的过程中,每遇到一个变量,就要进行标识符识别。标识符识别这个过程要决定从哪里获得数据或者存取数据。此过程搜索运行期上下文的作用域链,查找同名的标识符。搜索工作从运行函数的激活目标的作用域前端开始。如果找到了,就使用这个具有指定标识符的变量;如果没找到,搜索工作将进入作用域链的下一个对象,此过程持续运行,直到标识符被找到或者没有更多可用对象可用于搜索,这种情况视为标识符未定义。正是这种搜索过程影响了性能。如果想了解如何编写高性能的js,建议看看这篇blog,这个小结也是摘自于它——http://www.cnblogs.com/coco1s/p/4017544.html

    执行上下文

    在JavaScript中有三种代码运行环境:

    • Global Code
      • JavaScript代码开始运行的默认环境
    • Function Code
      • 代码进入一个JavaScript函数
    • Eval Code
      • 使用eval()执行代码

    为了表示不同的运行环境,JavaScript中有一个执行上下文(Execution context,EC)的概念。也就是说,当JavaScript代码执行的时候,会进入不同的执行上下文,这些执行上下文就构成了一个执行上下文栈(Execution context stack,ECS)

    执行上下文包含三个重要的概念,彼此联系且不好理解,导致了新手很难做到一次理解清楚,如下图所示:

    每个执行上下文都有三个重要的属性,变量对象(Variable object,VO),作用域链(Scope chain)和this,当然还有一些附加的属性。

    当一段JavaScript代码执行的时候,JavaScript解释器会创建Execution Context,其实这里会有两个阶段:

    • 创建阶段(当函数被调用,但是开始执行函数内部代码之前)
      • 创建Scope chain
      • 创建VO/AO(variables, functions and arguments)
      • 设置this的值
    • 激活/代码执行阶段
      • 设置变量的值、函数的引用,然后解释/执行代码

    这里想要详细介绍一下"创建VO/AO"中的一些细节,因为这些内容将直接影响代码运行的行为。

    对于"创建VO/AO"这一步,JavaScript解释器主要做了下面的事情:

    • 根据函数的参数,创建并初始化arguments object
    • 扫描函数内部代码,查找函数声明(Function declaration)
      • 对于所有找到的函数声明,将函数名和函数引用存入VO/AO中
      • 如果VO/AO中已经有同名的函数,那么就进行覆盖
    • 扫描函数内部代码,查找变量声明(Variable declaration)
      • 对于所有找到的变量声明,将变量名存入VO/AO中,并初始化为"undefined"
      • 如果变量名称跟已经声明的形式参数或函数相同,则变量声明不会干扰已经存在的这类属性

    看下面的例子:

    复制代码
    function foo(i) {
        var a = 'hello';
        var b = function privateB() {
    
        };
        function c() {
    
        }
    }
    
    foo(22);
    复制代码

    对于上面的代码,在"创建阶段",可以得到下面的Execution Context object:

    复制代码
    fooExecutionContext = {
        scopeChain: { ... },
        variableObject: {
            arguments: {
                0: 22,
                length: 1
            },
            i: 22,
            c: pointer to function c()
            a: undefined,
            b: undefined
        },
        this: { ... }
    }
    复制代码

    在"激活/代码执行阶段",Execution Context object就被更新为:

    复制代码
    fooExecutionContext = {
        scopeChain: { ... },
        variableObject: {
            arguments: {
                0: 22,
                length: 1
            },
            i: 22,
            c: pointer to function c()
            a: 'hello',
            b: pointer to function privateB()
        },
        this: { ... }
    }

    总结

    函数在定义时就会确定他的作用域与作用域链(静态),只有调用的时候才会创建一个执行上下文,其中包含了调用时的形参,其中的函数声明与变量(VO), 同时创建活动对象(AO),并将AO压入执行上下文的作用域链的最前端并且包含了this的属性,执行上下文的作用域链是通过正在被调用函数的作用域链得到的(动态)。

    以上的理解如有错误,敬请指正,敬礼~

    参考

    http://www.cnblogs.com/coco1s/p/4017544.html

    http://www.cnblogs.com/wilber2013/p/4909459.html

    http://www.cnblogs.com/wilber2013/p/4909430.html#_nav_3

  • 相关阅读:
    PHP基本的语法以及和Java的差别
    Linux 性能測试工具
    【Oracle 集群】Linux下Oracle RAC集群搭建之Oracle DataBase安装(八)
    【Oracle 集群】Oracle 11G RAC教程之集群安装(七)
    【Oracle 集群】11G RAC 知识图文详细教程之RAC在LINUX上使用NFS安装前准备(六)
    【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之RAC 特殊问题和实战经验(五)
    【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之缓存融合技术和主要后台进程(四)
    【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之RAC 工作原理和相关组件(三)
    Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之ORACLE集群概念和原理(二)
    【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之集群概念介绍(一)
  • 原文地址:https://www.cnblogs.com/nanchen/p/6055016.html
Copyright © 2011-2022 走看看