对于Javascript执行上下文的全面了解

  

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

  

  

  

<强>全局级别的代码- - - - - - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境。

  

<强>函数级别的代码- - - - - - 当执行一个函数时,运行函数体中的代码。

  

<强> Eval的代码- - - - - - 在Eval函数内运行的代码。

  

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

  

对于Javascript执行上下文的全面了解

  

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

  

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

  

  

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

  

对于Javascript执行上下文的全面了解

  

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

        (函数foo () {   如果(i===3) {   返回;   }   其他{   foo (+ + i);   }   } (0));      

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

  

对于Javascript执行上下文的全面了解

  

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

  

<强>单线程

  

同步执行

  

唯一的一个全局上下文

  

函数的执行上下文的个数没有限制

  

每次某个函数被调用,就会有个新的执行上下文为其创建,即使是调用的自身函数,也是如此。

  

  

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

  

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

  

建立变量,函数,参数对象,参数

  

建立作用域链

  这个的

确定值

  

代码执行阶段:

  

变量赋值,函数引用,执行其它代码

  

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

对于Javascript执行上下文的全面了解