JavaScript的执行过程(深入执行上下文、GO、AO、VO和VE等概念)
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 = 20
var result = num1 + num2
foo()
调用栈调用过程:
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函数执行上下文出栈,foo函数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等概念)的更多相关文章
- 将shell脚本的执行过程和执行结果导入到log文件中
[root@localhost scripts]# vim ping.sh #!/bin/bash set -x ##分步执行 exec &> /tmp/log.txt ##脚本执行的过 ...
- 高程(4):执行环境、作用域、上下文执行过程、垃圾收集、try...catch...
高程三 4.2.4.3 一.执行环境 1.全局执行环境是最外层的执行环境. 2.每个函数都有自己的执行环境,执行函数时,函数环境就会被推入一个当前环境栈中,执行完毕,栈将其环境弹出,把控制器返回给之前 ...
- 详细分析SQL语句逻辑执行过程和相关语法
本文目录: 1.SQL语句的逻辑处理顺序 1.2 各数据库系统的语句逻辑处理顺序 1.2.1 SQL Server和Oracle的逻辑执行顺序 1.2.2 MariaDB的逻辑执行顺序 1.2.3 M ...
- 20135337朱荟潼 Linux第八周学习总结——进程的切换和系统的一般执行过程
第八周 进程的切换和系统的一般执行过程 一.进程切换关键代码switch_to 1.不同类型进程有不同调度需求--两种分类 2.调度策略--规则 Linux中进程优先级是动态的,周期性调整. 3.时机 ...
- javascript执行环境(执行期上下文)详解
javascript执行环境(执行期上下文) 当js控制器(control)进入可执行代码时,控制器会进入一个执行环境,活动的多个执行环境构成执行环境栈,最上面的是正在运行的执行环境,当控制器进入一个 ...
- JavaScript 执行环境(执行上下文) 变量对象 作用域链 上下文 块级作用域 私有变量和特权方法
总结自<高程三>第四章 理解Javascript_12_执行模型浅析 JS的执行环境与作用域 javascript高级程序第三版学习笔记[执行环境.作用域] 在javascript ...
- 深入理解JavaScript执行上下文、函数堆栈、提升的概念
本文内容主要转载自以下两位作者的文章,如有侵权请联系我删除: https://feclub.cn/post/content/ec_ecs_hosting http://blog.csdn.net/hi ...
- javascript代码解释执行过程
javascript是由浏览器解释执行的脚本语言,不同于java c,需要先编译后运行,javascript 由浏览器js解释器进行解释执行,总的过程分为两大块,预编译期和执行期 下面的几个demo解 ...
- Javascript的执行过程详细研究
下面我们以更形象的示例来说明JavaScript代码在页面中的执行顺序.如果说,JavaScript引擎的工作机制比较深奥是因为它属于底层行为,那么JavaScript代码执行顺序就比较形象了,因为我 ...
随机推荐
- Git忽略提交规则 .gitignore文件
在使用Git的过程中,我们喜欢有的文件比如日志,临时文件,编译的中间文件等不要提交到代码仓库,这时就要设置相应的忽略规则,来忽略这些文件的提交.简单来说一个场景:在你使用git add .的时候,遇到 ...
- layout_weight属性分析
最近写Demo,突然发现了Layout_weight这个属性,发现网上有很多关于这个属性的有意思的讨论,可是找了好多资料都没有找到一个能够说的清楚的,于是自己结合网上资料研究了一下,终于迎刃而解,写出 ...
- 尚硅谷SSM-CRUD实战Demo
SSM-CRUD实战项目 1. 项目总览 SpringMVC + Spring + MyBatis CRUD:增删改查 功能: 分页 数据校验 jquery前端校验+JSR303后端校验 ajax R ...
- LuoguP7713 「EZEC-10」打分 题解
Content 某个人去参加比赛,\(n\) 个评委分别给他打分 \(a_1,a_2,\dots,a_n\).这个人可以最多执行 \(m\) 次操作,每次操作将一个评委的分数加 \(1\).定义他的最 ...
- CF1438A Specific Tastes of Andre 题解
Content 如果一个序列的和能够被它的长度整除,我们称这个序列是不错的.如果一个序列的所有的非空子序列都是不错的,我们就称这个序列是完美的.现在有 \(t\) 组询问,每组询问给定一个整数 \(n ...
- java 多线程: Thread 并发访问-代码块同步synchronized {};String作为被锁的对象
方法同步的弊端 方法同步的时候,如果一个方法需要线程安全控制的代码速度其实很快,但是还有其他的业务逻辑代码耗时非常长(比如网络请求),这样所有的线程就在这一块就等待着了,这样造成了极大的资源浪费如果并 ...
- Flink的窗口处理机制(一)
一.为什么需要 window ? 在流处理应用中,数据是连续不断的,即数据是没有边界的,因此我们不可能等到所有数据都到了才开始处理.当然我们可以每来一个消息就处理一次,但是有时我们需要做一些聚合类的处 ...
- 手把手教你如何使用 webpack5 的模块联邦新特性
想象一下,在webpack5还没出来前,前端使用第三方组件库,例如使用 dayjs 日期处理库,都是通过 npm i dayjs -s 安装 dayjs 模块到项目里,然后就可以通过 require ...
- cmake以源码的方式引入第三方项目
最前 本文将介绍一种以源码的方式引入第三方库的方法 准备 主项目,需要引用第三方库的某些函数 第三方库,以源码的形式提供给主项目使用 注意: 本文的背景:已经将第三方源码下载好. 一个例子 我这里准备 ...
- NDK编译可执行文件在Android 中运行显示error: only position independent executables (PIE) are supported.失败问题解决办法。
由于使用了NDK编译的可执行文件在应用中调用,在Android 7.0上的运行情况发现,当运行该可执行文件时,报如下错误: error: only position independent execu ...