在这篇文章中,将比较深入地阐述下执行上下文 - 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引擎内部,这个上下文的创建过程具体分为两个阶段:

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

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

建立作用域链

确定this的值

代码执行阶段:

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

实际上,可以把执行上下文看做一个对象,其下包含了以上3个属性:
executionContextObj = {
   variableObject: { /* 函数中的arguments对象, 参数, 内部的变量以及函数声明 */ },
   scopeChain: { /* variableObject 以及所有父执行上下文中的variableObject */ },
   this: {}
 }

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

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

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

找到当前上下文中的调用函数的代码

在执行被调用的函数体中的代码以前,开始创建执行上下文

进入第一个阶段-建立阶段:

建立variableObject对象:

建立arguments对象,检查当前上下文中

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。

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

(转载自http://blogread.cn/it/article/6178)

深入理解Javascript之执行上下文(Execution Context)的更多相关文章

  1. 理解Javascript之执行上下文(Execution Context)

    1>什么是执行上下文 Javascript中代码的运行环境分为以下三种: 全局级别的代码 - 这个是默认的代码运行环境,一旦代码被载入,引擎最先进入的就是这个环境. 函数级别的代码 - 当执行一 ...

  2. 深入理解JavaScript系列+ 深入理解javascript之执行上下文

    http://www.cnblogs.com/TomXu/archive/2011/12/15/2288411.html http://blog.csdn.net/hi_kevin/article/d ...

  3. 前端知识体系:JavaScript基础-原型和原型链-理解JavaScript的执行上下文栈,可以应用堆栈信息快速定位问题

    理解JavaScript的执行上下文栈,可以应用堆栈信息快速定位问题(原文文档) 1.什么是执行上下文: 简而言之,执行上下文就是当前JavaScript代码被解析和执行时所在环境的抽象概念,Java ...

  4. Javascript 的执行环境(execution context)和作用域(scope)及垃圾回收

    执行环境有全局执行环境和函数执行环境之分,每次进入一个新执行环境,都会创建一个搜索变量和函数的作用域链.函数的局部环境不仅有权访问函数作用于中的变量,而且可以访问其外部环境,直到全局环境.全局执行环境 ...

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

    本文转自:http://blogread.cn/it/article/6178 在这篇文章中,将比较深入地阐述下执行上下文 - Javascript中最基础也是最重要的一个概念.相信读完这篇文章后,你 ...

  6. 【深入理解javascript】执行上下文

    参考原文:执行上下文 1.每一个执行上下文,工作分为三个阶段: 准备阶段–>执行阶段–>调用阶段 准备阶段:代码执行之前,设置数据,相当于初始化. 执行阶段:开始执行每一行代码. 调用阶段 ...

  7. JavaScript的执行上下文,真没你想的那么难

    作者:小土豆 博客园:https://www.cnblogs.com/HouJiao/ 掘金:https://juejin.im/user/2436173500265335 前言 在正文开始前,先来看 ...

  8. JavaScript的执行上下文

    在JavaScript的运行过程中,经常会遇到一些"奇怪"的行为,不理解为什么JavaScript会这么工作. 这时候可能就需要了解一下JavaScript执行过程中的相关内容了. ...

  9. 深入理解javascript中执行环境(作用域)与作用域链

    深入理解javascript中执行环境(作用域)与作用域链 相信很多初学者对与javascript中的执行环境与作用域链不能很好的理解,这里,我会按照自己的理解同大家一起分享. 一般情况下,我们把执行 ...

随机推荐

  1. FPGA基础学习(5) -- 时序约束(实践篇)

    目录 1. 理论回顾 2. 时间裕量 3. 最大延迟和最小延迟 4. 案例分析 参考文献: 距离上一篇有关时序的理论篇已经有一段时间了(可以参考博文FPGA时序约束--理论篇),实际上此段时间,甚至到 ...

  2. 对结构化学习(structured learning)的理解

    接触深度学习以来一直接触的概念都是回归,分类,偶尔接触到结构化学习的概念,似懂非懂的糊弄过去,实在是不负责的表现 翻阅维基百科https://en.wikipedia.org/wiki/Structu ...

  3. c# 多数值区间判断是否有重叠

    /// <summary> /// 金额区间判断帮助类 /// </summary> public static class DecimalRangeHelper { /// ...

  4. Oracle中对XMLType的简单操作(extract、extractvalue)

    前几天一直在做Oracle对XMLType字段的操作,我还不是Oracle大拿,到网上找了很多资料,但是很多就是单一功能的介绍,不能很好的解决问题,现在在这里总结下. 1.下面先创建一个名未test. ...

  5. pyspider的基本操作

    一.安装 pip install pyspider 1.修改 pyspider/webui/webdav.py 第209行:否则启动会报错 把: 'domaincontroller': NeedAut ...

  6. sqlmap命令小结

    --technique 这个参数可以指定sqlmap使用的探测技术,默认情况下会测试所有的方式. 支持的探测方式如下: B: Boolean-based blind SQL injection(布尔型 ...

  7. poj3187

    一.题意:给定n,求1~n的一个排列,这个排列需要满足以下两个要求:1.杨辉三角最后的和为sum  2.字典序最小 二.思路:暴力枚举每一个排列,然后计算和并与sum进行比较.这里我比较费解的是为什么 ...

  8. 微信小程序实现图片上传功能

    前端: 微信开发者工具 后端:.Net 服务器:阿里云 这里介绍微信小程序如何实现上传图片到自己的服务器上 前端代码 data: { productInfo: {} }, //添加Banner bin ...

  9. PIE SDK分类统计

    1. 算法功能简介 分类统计功能是将分类后的结果统计输出. PIE SDK支持算法功能的执行,下面对分类统计算法功能进行介绍. 2. 算法功能实现说明 2.1. 实现步骤 第一步 算法参数设置 第二步 ...

  10. Kafka消费不到数据的特殊情况

    我大约是把kafka消费不到数据的特殊情况都经历了一遍了吧= =. kafka消费不到数据的原因,首先检查配置之类的,如是否设置了group.id,对应的topic是否正确等等,这些不多说. 下面是我 ...