对于Javascript 执行上下文的理解

转载无源头地址

在这篇文章中,将比较深入地阐述下执行上下文 – JavaScript中最基础也是最重要的一个概念。相信读完这篇文章后,你就会明白javascript引擎内部在执行代码以前到底做了些什么,为什么某些函数以及变量在没有被声明以前就可以被使用,以及它们的最终的值是怎样被定义的。

什么是执行上下文

Javascript中代码的运行环境分为以下三种:

  • 全局级别的代码 – 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境。
  • 函数级别的代码 – 当执行一个函数时,运行函数体中的代码。
  • Eval的代码 – 在Eval函数内运行的代码。

在网上可以找到很多阐述作用域的资源,为了使该文便于大家理解,我们可以将“执行上下文”看做当前代码的运行环境或者作用域。下面我们来看一个示例,其中包括了全局以及函数级别的执行上下文:

上图中,一共用4个执行上下文。紫色的代表全局的上下文;绿色代表person函数内的上下文;蓝色以及橙色代表person函数内的另外两个函数的上下文。注意,不管什么情况下,只存在一个全局的上下文,该上下文能被任何其它的上下文所访问到。也就是说,我们可以在person的上下文中访问到全局上下文中的sayHello变量,当然在函数firstName或者lastName中同样可以访问到该变量。

至于函数上下文的个数是没有任何限制的,每到调用执行一个函数时,引擎就会自动新建出一个函数上下文,换句话说,就是新建一个局部作用域,可以在该局部作用域中声明私有变量等,在外部的上下文中是无法直接访问到该局部作用域内的元素的。在上述例子的,内部的函数可以访问到外部上下文中的声明的变量,反之则行不通。那么,这到底是什么原因呢?引擎内部是如何处理的呢?

执行上下文堆栈

在浏览器中,javascript引擎的工作方式是单线程的。也就是说,某一时刻只有唯一的一个事件是被激活处理的,其它的事件被放入队列中,等待被处理。下面的示例图描述了这样的一个堆栈:

我们已经知道,当javascript代码文件被浏览器载入后,默认最先进入的是一个全局的执行上下文。当在全局上下文中调用执行一个函数时,程序流就进入该被调用函数内,此时引擎就会为该函数创建一个新的执行上下文,并且将其压入到执行上下文堆栈的顶部。浏览器总是执行当前在堆栈顶部的上下文,一旦执行完毕,该上下文就会从堆栈顶部被弹出,然后,进入其下的上下文执行代码。这样,堆栈中的上下文就会被依次执行并且弹出堆栈,直到回到全局的上下文。请看下面一个例子:

    
        (function foo(i) {
            if (i === 3) {
                return;
            }
            else {
                foo(++i);
            }
        }(0));
    

上述foo被声明后,通过()运算符强制直接运行了。函数代码就是调用了其自身3次,每次是局部变量i增加1。每次foo函数被自身调用时,就会有一个新的执行上下文被创建。每当一个上下文执行完毕,该上上下文就被弹出堆栈,回到上一个上下文,直到再次回到全局上下文。真个过程抽象如下图:

由此可见 ,对于执行上下文这个抽象的概念,可以归纳为以下几点:

  • 单线程
  • 同步执行
  • 唯一的一个全局上下文
  • 函数的执行上下文的个数没有限制
  • 每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

执行上下文的建立过程

我们现在已经知道,每当调用一个函数时,一个新的执行上下文就会被创建出来。然而,在javascript引擎内部,这个上下文的创建过程具体分为两个阶段:

  1. 建立阶段(发生在当调用一个函数时,但是在执行函数体内的具体代码以前)

    • 建立变量,函数,arguments对象,参数
    • 建立作用域链
    • 确定this的值
  2. 代码执行阶段:
    • 变量赋值,函数引用,执行其它代码

实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:

    
          (executionContextObj = {
            variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ },
            scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ },
            this: {}
          }
    

建立阶段以及代码执行阶段的详细分析

确切地说,执行上下文对象(上述的executionContextObj)是在函数被调用时,但是在函数体被真正执行以前所创建的。函数被调用时,就是我上述所描述的两个阶段中的第一个阶段 – 建立阶段。这个时刻,引擎会检查函数中的参数,声明的变量以及内部函数,然后基于这些信息建立执行上下文对象(executionContextObj)。在这个阶段,variableObject对象,作用域链,以及this所指向的对象都会被确定。

上述第一个阶段的具体过程如下:

  1. 找到当前上下文中的调用函数的代码
  2. 在执行被调用的函数体中的代码以前,开始创建执行上下文
  3. 进入第一个阶段-建立阶段:
    • 建立variableObject对象:

      1. 建立arguments对象,检查当前上下文中的参数,建立该对象下的属性以及属性值
      2. 检查当前上下文中的函数声明:

        每找到一个函数声明,就在variableObject下面用函数名建立一个属性,属性值就是指向该函数在内存中的地址的一个引用

        如果上述函数名已经存在于variableObject下,那么对应的属性值会被新的引用所覆盖。

    • 初始化作用域链
    • 确定上下文中this的指向对象
  4. 代码执行阶段:

    执行函数体中的代码,一行一行地运行代码,给variableObject中的变量属性赋值。

下面来看个具体的代码示例:

    
        function foo(i) {
            var a = ‘hello‘;
            var b = function privateB() {

            };
            function c() {

            }
        }

        foo(22);
    

在调用foo(22)的时候,建立阶段如下:

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

由此可见,在建立阶段,除了arguments,函数的声明,以及参数被赋予了具体的属性值,其它的变量属性默认的都是undefined。一旦上述建立阶段结束,引擎就会进入代码执行阶段,这个阶段完成后,上述执行上下文对象如下:

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

我们看到,只有在代码执行阶段,变量属性才会被赋予具体的值。

局部变量作用域提升的缘由

在网上一直看到这样的总结: 在函数中声明的变量以及函数,其作用域提升到函数顶部,换句话说,就是一进入函数体,就可以访问到其中声明的变量以及函数。这是对的,但是知道其中的缘由吗?相信你通过上述的解释应该也有所明白了。不过在这边再分析一下。看下面一段代码:

    
        (function() {
            console.log(typeof foo); // function pointer
            console.log(typeof bar); // undefined

            var foo = ‘hello‘,
                bar = function() {
                    return ‘world‘;
                };

            function foo() {
                return ‘hello‘;
            }

        }());?
    

上述代码定义了一个匿名函数,并且通过()运算符强制理解执行。那么我们知道这个时候就会有个执行上下文被创建,我们看到例子中马上可以访问foo以及bar变量,并且通过typeof输出foo为一个函数引用,bar为undefined。

为什么我们可以在声明foo变量以前就可以访问到foo呢?

因为在上下文的建立阶段,先是处理arguments, 参数,接着是函数的声明,最后是变量的声明。那么,发现foo函数的声明后,就会在variableObject下面建立一个foo属性,其值是一个指向函数的引用。当处理变量声明的时候,发现有var foo的声明,但是variableObject已经具有了foo属性,所以直接跳过。当进入代码执行阶段的时候,就可以通过访问到foo属性了,因为它已经就存在,并且是一个函数引用。

为什么bar是undefined呢?

因为bar是变量的声明,在建立阶段的时候,被赋予的默认的值为undefined。由于它只要在代码执行阶段才会被赋予具体的值,所以,当调用typeof(bar)的时候输出的值为undefined。

好了,到此为止,相信你应该对执行上下文有所理解了,这个执行上下文的概念非常重要,务必好好搞懂之!

时间: 2024-10-16 00:44:49

对于Javascript 执行上下文的理解的相关文章

深入理解javascript执行上下文(Execution Context)

本文转自:http://blogread.cn/it/article/6178 在这篇文章中,将比较深入地阐述下执行上下文 - Javascript中最基础也是最重要的一个概念.相信读完这篇文章后,你就会明白javascript引擎内部在执行代码以前到底做了些什么,为什么某些函数以及变量在没有被声明以前就可以被使用,以及它们的最终的值是怎样被定义的. 什么是执行上下文 Javascript中代码的运行环境分为以下三种: 全局级别的代码 - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的

Javascript 执行上下文 context&scope

执行上下文(Execution context) 执行上下文可以认为是 代码的执行环境. 1 当代码被载入的时候,js解释器 创建一个 全局的执行上下文. 2 当执行函数时,会创建一个 函数的执行上下文. 3 当执行 eval()的时候,创建 一个 eval 执行上下文. # if,for,while 等块不会创建 execution context,从而不会创建 scope. 当js解释器开始工作的时候: 1 首先创建一个 执行上下文栈(后进先出) 2 接着创建一个 全局的执行上下文,并放入执

javascript执行上下文

javascript上下文应该算是js这门语言基础中的基础,如果理解了它,就能比较清晰地理解js语句执行的流程,更好地分析代码. 上下文分类: 1)全局级别的上下文  2)函数级别的上下文  3)eval函数内运行的上下文 执行上下文堆栈 在<javascript高级程序设计>中,上下文被称为执行环境,其实表达的都是同一个东西. 在浏览器中,javascript引擎的工作方式是单线程的.也就是说,某一时刻只有唯一的一个事件是被激活处理的,其它的事件被放入队列中,等待被处理. 我们已经知道,当j

javascript执行上下文环境

今天,想对javascript的执行上下文环境做一个深入的理解.之前一直都有这打算的,但无奈忙着忙着就忘记了.今天看了一篇博客,对执行上下文环境的理解可以说是醍醐灌顶. 一.对一段代码的理解开始 在浏览器的控制台输入以下代码段: 这个结果说明,代码在一句句执行之前,浏览器已经做了一些准备工作,所以,后面两个都没有报错.下面再看一段代码: 这个结果说明,函数声明时,把整个函数赋值了,而函数表达式和变量一样,只是申明. 我们总结一下,在"准备工作"中完成了哪些工作: 变量.函数表达式--变

再看javascript执行上下文、变量对象

突然看到一篇远在2010年的老文,作者以章节的形式向我们介绍了ECMA-262-3的部分内容,主要涉及到执行上下文.变量对象.作用域.this等语言细节.内容短小而精悍,文风直白而严谨,读完有酣畅淋漓.醍醐灌顶之感,强烈推荐!!! 原文链接:这里 本想翻译成文,原来早已有人做了,这里.真生不逢时,何其遗憾啊! 做个笔记,聊慰我心. 执行上下文 ExecutionContext 每当控制器(control)转换到ECMAScript可执行代码时,都会创建并进入到一个可执行上下文. 一段简短的句子,

深入理解Javascript之执行上下文(Execution Context)

在这篇文章中,将比较深入地阐述下执行上下文 - Javascript中最基础也是最重要的一个概念.相信读完这篇文章后,你就会明白javascript引擎内部在执行代码以前到底做了些什么,为什么某些函数以及变量在没有被声明以前就可以被使用,以及它们的最终的值是怎样被定义的.什么是执行上下文 Javascript中代码的运行环境分为以下三种: 全局级别的代码 - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境. 函数级别的代码 - 当执行一个函数时,运行函数体中的代码. Eva

Javascript的“上下文”(context)

一:JavaScript中的“上下文“指的是什么 百科中这样定义: 上下文是从英文context翻译过来,指的是一种环境. 在软件工程中,上下文是一种属性的有序序列,它们为驻留在环境内的对象定义环境. 在对象的激活过程中创建上下文,对象被配置为要求某些自动服务,如同步.事务.实时激活.安全性等等.又比如计算机技术中,相对于进程而言,上下文就是进程执行时的环境. 具体来说就是各个变量和数据,包括所有的寄存器变量.进程打开的文件.内存信息等. JavaScript的执行上下文的理解是一种大概模糊的理

js基础梳理-究竟什么是执行上下文栈(执行栈),执行上下文(可执行代码)?

日常在群里讨论一些概念性的问题,比如变量提升,作用域和闭包相关问题的时候,经常会听一些大佬们给别人解释的时候说执行上下文,调用上下文巴拉巴拉,总有点似懂非懂,不明觉厉的感觉.今天,就对这两个概念梳理一下,加深对js基础核心的理解. 1. 执行上下文(execution context)与可执行代码(execution code) 1.1 首先说一下,可执行代码的类型有哪些: 全局代码:例如加载外部的js文件或者本地标签内的代码.全局代码不包括 function 体内的代码 函数代码:functi

执行上下文

上下文的原意是 context, 作用域的原意是scope, 这两个不是一个东西. 每一个函数的调用(function invocation) 都有对应的scope 和context. scope 指的是 函数被调用的时候, 各个变量的作用区域context 指的是 current scope and its enclosing scope. 就是当前scope 和包裹它外面的scope. 如果一个变量在当前scope没找到,那么它会自底向上继续找enclosing scope 直到找到为止.很