目录

  • JavaScript的执行过程
    • 前言
    • 1.初始化全局对象
    • 2.执行上下文栈(调用栈)
    • 3.调用栈调用GEC的过程
    • 4.函数执行上下文
    • 5.变量环境和记录
    • 6.全局代码执行过程(函数嵌套)
    • 总结:

JavaScript的执行过程

前言

编写一段JavaScript代码,它是如何执行的呢?简单来说,JS引擎在执行JavaScript代码的过程中需要先解析再执行。那么在解析阶段JS引擎又会进行哪些操作,接下来就一起来了解一下JavaScript在执行过程中的详细过程,包括执行上下文、GO、AO、VO和VE等概念的理解。

1.初始化全局对象

首先,JS引擎会在执行代码之前,也就是解析代码时,会在我们的堆内存创建一个全局对象:Global Object(简称GO),观察以下代码,在全局中定义了几个变量:

示例代码:

var name = 'curry'
var message = 'I am a coder'
var num = 30

JS引擎内部在解析以上代码时,会创建一个全局对象(伪代码如下):

  • 所有的**作用域(scope)**都可以访问该全局对象;
  • 对象里面会包含一些全局的方法和类,像Math、Date、String、Array、setTimeout等等;
  • 其中有一个window属性是指向该全局对象自身的;
  • 该对象中会收集我们上面全局定义的变量,并设置成undefined;
  • 全局对象是非常重要的,我们平时之所以能够使用这些全局方法和类,都是在这个全局对象中获取的;
var GlobalObject = {Math: '类',Date: '类',String: '类',setTimeout: '函数',setInterval: '函数',window: GlobalObject,...name: undefined,message: undefined,num: undefined
}

2.执行上下文栈(调用栈)

了解了什么是全局对象后,下面就来聊聊代码具体执行的地方。JS引擎为了执行代码,引擎内部会有一个执行上下文栈(Execution Context Stack,简称ECS),它是用来执行代码的调用栈

(1)ECS如何执行?先执行谁呢?

  • 无疑是先执行我们的全局代码块;
  • 在执行前全局代码会构建一个全局执行上下文(Global Execution Context,简称GEC)
  • 一开始GEC就会被放入到ECS中执行;

(2)那么全局执行上下文(GEC)包含那些内容呢?

  • **第一部分:**执行代码前。

    • 在转成抽象语法树之前,会将全局定义的变量、函数等加入到Global Object中,也就是上面初始化全局对象的过程;
    • 但是并不会真正赋值(表现为undefined),所以这个过程也称之为变量的作用域提升(hoisting)
  • **第二部分:**代码执行。
    • 对变量进行赋值,或者执行其它函数等;

下面就通过一幅图,来看看GEC被放入ECS后的表现形式:

3.调用栈调用GEC的过程

接下来,将全局代码复杂化一点,再来看看调用栈调用全局执行上下文(GEC)的过程。

实例代码:

var name = 'curry'console.log(message)var message = 'I am a coder'function foo() {var name = 'foo'console.log(name)
}var num1 = 30
var num2 = 20var result = num1 + num2foo()

调用栈调用过程:

  • 1.初始化全局对象。

    • 这里需要注意的是函数存放的是地址,会指向函数对象,与普通变量有所不同;
    • 从上往下解析JS代码,当解析到foo函数时,因为foo不是普通变量,并不会赋为undefined,JS引擎会在堆内存中开辟一块空间存放foo函数,在全局对象中引用其地址;
    • 这个开辟的函数存储空间最主要存放了该函数的父级作用域和函数的执行体代码块

  • 2.构建一个全局执行上下文(GEC),代码执行前将VO的内存地址指向GlobalObject(GO)。

  • 3.将全局执行上下文(GEC)放入执行上下文栈(ECS)中。

  • 4.从上往下开始执行全局代码,依次对GO对象中的全局变量进行赋值。

    • 当执行var name = 'curry'时,就从VO(对应的就是GO)中找到name属性赋值为curry;
    • 接下来执行console.log(message),就从VO中找到message,注意此时的message还为undefined,因为message真正赋值在下一行代码,所以就直接打印undefined(也就是我们经常说的变量作用域提升);
    • 后面就依次进行赋值,执行到var result = num1 + num2,也是从VO中找到num1和num2两个属性的值进行相加,然后赋值给result,result最终就为50;
    • 最后执行到foo(),也就是需要去执行foo函数了,这里的操作是比较特殊的,涉及到函数执行上下文,下面来详细了解;

4.函数执行上下文

在执行全局代码遇到函数如何执行呢?

  • 在执行的过程中遇到函数,就会根据函数体创建一个函数执行上下文(Functional Execution Context,简称FEC),并且加入到执行上下文栈(ECS)中。
  • 函数执行上下文(FEC)包含三部分内容:
    • AO:在解析函数时,会创建一个Activation Objec(AO)
    • 作用域链:由函数VO和父级VO组成,查找是一层层往外层查找;
    • this指向:this绑定的值,在函数执行时确定;
  • 其实全局执行上下文(GEC)也有自己的作用域链和this指向,只是它对应的作用域链就是自己本身,而this指向为window。

继续来看上面的代码执行,当执行到foo()时:

  • 先找到foo函数的存储地址,然后解析foo函数,生成函数的AO;
  • 根据AO生成函数执行上下文(FEC),并将其放入执行上下文栈(ECS)中;
  • 开始执行foo函数内代码,依次找到AO中的属性并赋值,当执行console.log(name)时,就会去foo的VO(对应的就是foo函数的AO)中找到name属性值并打印;

5.变量环境和记录

上文中提到了很多次VO,那么VO到底是什么呢?下面从ECMA新旧版本规范中来谈谈VO。

在早期ECMA的版本规范中:每一个执行上下文会被关联到一个变量环境(Variable Object,简称VO),在源代码中的变量和函数声明会被作为属性添加到VO中。对应函数来说,参数也会被添加到VO中。

  • 也就是上面所创建的GO或者AO都会被关联到变量环境(VO)上,可以通过VO查找到需要的属性;
  • 规定了VO为Object类型,上文所提到的GO和AO都是Object类型;

在最新ECMA的版本规范中:每一个执行上下文会关联到一个变量环境(Variable Environment,简称VE),在执行代码中变量和函数的声明会作为**环境记录(Environment Record)**添加到变量环境中。对于函数来说,参数也会被作为环境记录添加到变量环境中。

  • 也就是相比于早期的版本规范,对于变量环境,已经去除了VO这个概念,提出了一个新的概念VE;
  • 没有规定VE必须为Object,不同的JS引擎可以使用不同的类型,作为一条环境记录添加进去即可;
  • 虽然新版本规范将变量环境改成了VE,但是JavaScript的执行过程还是不变的,只是关联的变量环境不同,将VE看成VO即可;

6.全局代码执行过程(函数嵌套)

了解了上面相关的概念和调用流程之后,就来看一下存在函数嵌套调用的代码是如何执行的,以及执行过程中的一些细节,以下面代码为例:

var message = 'global'function foo(m) {var message = 'foo'console.log(m)function bar() {console.log(message)}bar()
}foo(30)
  • 初始化全局对象(GO),执行全局代码前创建GEC,并将GO关联到VO,然后将GEC加入ECS中:

    • foo函数存储空间中指定的父级作用域为全局对象;

  • 开始执行全局代码,从上往下依次给全局属性赋值:

    • 给message属性赋值为global;

  • 执行到foo函数调用,准备执行foo函数前,创建foo函数的AO:

    • bar函数存储空间中指定父级作用域为foo函数的AO;

  • 创建foo函数的FEC,并加入到ECS中,然后开始执行foo函数体内的代码:

    • 根据foo函数调用的传参,给形参m赋值为30,接着给message属性赋值为foo;
    • 所以,m打印结果为30;

  • 执行到bar函数调用,准备执行bar函数前,创建bar函数的AO:

    • bar函数中没有定义属性和声明函数,以空对象表示;

  • 创建bar函数的FEC,并加入到ECS中,然后开始执行bar函数体内的代码:

    • 执行console.log(message),会先去bar函数自己的VO中找message,没有找到就往上层作用域的VO中找;
    • 这里bar函数的父级作用域为foo函数,所以找到foo函数VO中的message为foo,打印结果为foo

  • 全局中所有代码执行完成,bar函数执行上下文出栈,bar函数AO对象失去了引用,进行销毁。

  • 接着foo函数执行上下文出栈,foo函数AO对象失去了引用,进行销毁,同样,foo函数AO对象销毁后,bar函数的存储空间也失去引用,进行销毁。

总结:

  • 函数在执行前就已经确定了其父级作用域,与函数在哪执行没有关系,以函数声明的位置为主;

  • 执行代码查找变量属性时,会沿着作用域链一层层往上查找(沿着VO往上找),如果一直找到全局对象中还没有该变量属性,就会报错未定义;

  • 上文中提到了很多概念名词,下面来总结一下:

    名词 解释
    ECS 执行上下文栈(Execution Context Stack),也可称为调用栈,以栈的形式调用创建的执行上下文
    GEC 全局执行上下文(Global Execution Context),在执行全局代码前创建
    FEC 函数执行上下文(Functional Execution Context),在执行函数前创建
    VO Variable Object,早期ECMA规范中的变量环境,对应Object
    VE Variable Environment,最新ECMA规范中的变量环境,对应环境记录
    GO 全局对象(Global Object),解析全局代码时创建,GEC中关联的VO就是GO
    AO 函数对象(Activation Object),解析函数体代码时创建,FEC中关联的VO就是AO

JavaScript的执行过程(深入执行上下文、GO、AO、VO和VE等概念)相关推荐

  1. 深入理解MySQL执行过程及执行顺序

    MySQL在我们的开发中基本每天都要面对的,作为开发中的数据的来源,MySQL承担者存储数据和读写数据的职责.因为学习和了解MySQL是至关重要的,那么当我们在客户端发起一个SQL到出现详细的查询数据 ...

  2. 【Android 异步操作】AsyncTask 异步任务 ( FutureTask 模拟 AsyncTask 执行过程 | AsyncTask 执行过程回顾 | FutureTask 分析 )

    文章目录 一.FutureTask 使用流程 二.FutureTask 模拟 AsyncTask 执行过程 三.AsyncTask 执行过程回顾 四.FutureTask 分析 一.FutureTas ...

  3. servlet请求的执行过程_Springmvc执行流程

    Springmvc自出道以来便以其简单易用,功能强大而闻名于java界,借着其亲爹spring的名头迅速流行起来,可怜昔日的老大structs2频频被曝漏洞,加上使用的复杂性,逐渐日暮西山 ,被spr ...

  4. MySQL执行过程及执行顺序

    一.MySQL执行过程 MySQL整体的执行过程如下图所示 1 1.1 连接器 1.1.1连接器的主要职责 1.负责与客户端的通信,是半双工模式,这就意味着某一固定时刻只能由客户端向服务器请求或者服务 ...

  5. 20135337朱荟潼 Linux第八周学习总结——进程的切换和系统的一般执行过程

    第八周 进程的切换和系统的一般执行过程 一.进程切换关键代码switch_to 1.不同类型进程有不同调度需求--两种分类 2.调度策略--规则 Linux中进程优先级是动态的,周期性调整. 3.时机 ...

  6. Mybatis(9)Dao实现类和无实现类的执行过程

    findAll方法执行为例 1.执行findAll方法,DefaultSqlSession类 2. CachingExecutor类 3.BaseExecutor类 4.SimpleExecutor类 ...

  7. 深度揭秘 Promise 微任务和执行过程

    Promise 大伙太熟悉了,不过这里不讲大伙都知道的表面简单知识,而是一起来深入剖析 Promise 的注册微任务和执行的完整过程.能正确的使用 Promise 且能做到知其然知其所以然~ 本文分为 ...

  8. MySQL语句执行过程

    目录 前言 MySQL语句执行过程 一.连接器 二.查询缓存 三.分析器 四.优化器 1.逻辑查询优化 1.1 逻辑查询优化思路. 2.2 查询重写规则 2.3 启发式规则再逻辑优化阶段的应用 2.3 ...

  9. 计算机基础:14、计算机指令执行过程

    计算机基础:14.计算机指令执行过程 指令执行过程 CPU的流水线设计 指令执行过程 指令执行过程主要细分为三个步骤: 取指令 分析指令 执行指令 每一个步骤的详细操作如下 取指令阶段:1.指令缓存中 ...

最新文章

  1. Spring Boot 配置文件详解
  2. linux zabbix使用教程,《Zabbix安装部署》-Centos7
  3. 山东大学2020计算机考研学硕,2020山东大学考研学硕复试分数线
  4. python和环境变量_python 读取和设置环境变量
  5. W-Cms XSS和遍历目录漏洞
  6. [转载] Python 继承
  7. 【python】命令行参数argparse用法详解
  8. 按键精灵注册服务器,按键精灵网页注册脚本教程
  9. 备考OCJP认证知识点总结(四)
  10. 将安卓手机投屏到win10上
  11. APICloud Studio 在海马玩模拟器上调试程序
  12. 第二十七课 awk工具
  13. SpringBoot之DispatcherServlet详解及源码解析
  14. Linux 用户和用户组详解
  15. Android中HTTPS之一(三)具体操作(代码实现)
  16. 高中计算机期末试题,高中信息技术期末试卷
  17. 倍福TwinCAT(贝福Beckhoff)常见问题(FAQ)-人机界面快速入门 TC3
  18. VASP计算--INCAR参数详解以及INCAR模板和例子,铜的INCAR
  19. VBA下弹框打开excel文件并读取数据
  20. 为中国智造注入新动力 金智维获高瓴创投领投逾2亿元B轮融资

热门文章

  1. PWM控制的基本原理及相关概念
  2. php 判断下载状态,关于php:如何检测文件的下载是否已经完成?
  3. 在webView中使用JAVAScript定位
  4. 【Python】我用python爬取一月份微博热搜数据来分析人们对新型肺炎的关注程度变化
  5. 《CLR via C#》读书笔记---09 参数
  6. 动漫《那年那兔那些事儿》弹幕爬虫采集数据分析
  7. Mixly遥控器调节LED灯亮度
  8. An Introduction to Kernel Synchronization [LKD 09]
  9. 计算机网络谢希仁第七版答案
  10. 【半导体先进工艺制程技术系列】FinFET工艺流程