从JS执行原理开始理解作用域 —— JS执行过程

前言

通过上一篇文章我们大致知道了JS引擎的相关原理(从JS执行原理开始理解作用域 ------ V8引擎原理篇),接下来我们会对JS代码的执行过程进行一个具体分析。

JavaScript执行过程

假如我们有以下的一串代码,具体该如何执行呢?

javaScript 复制代码
var num1 = 1

function test() {
    var num1 = 2
    console.log(num1)
}

var num2 = 3
var num3 = 4

var result = num2 + num3

console.log(result)

test()

初始化全局对象

在js引擎执行代码之前,首先会在堆内存中创建一个全局对象:Global Object(GO);

这个全局对象在所有的作用域 中都可以被访问到,而且有一个访问自身的属性window。这个全局对象包含了一系列的属性,具体可以通过window属性进行查看:

执行上下文栈

在JS引擎内部有一个执行上下文栈(ECS,Execution Context Stack 即执行上下文栈),这个东西就是用来执行代码的调用栈,按照后进先出的原则进行执行。

这个执行栈回去执行全局的代码块,这时会构建一个GEC(Global Execution Context,即全局执行上下文) ,这个GEC会被放到ECS中进行执行。

那么,什么是GEC呢?

GEC就是所有不在函数内部的JavaScript代码,每个 JS 文件只会有一个全局执行上下文。

GEC被放到ECS中包含2部分的内容:

  1. 代码执行前,parser转成AST的过程中 ,会将全局定义的变量、函数加入到GO里,但是不会进行赋值。也叫做变量提升
  2. 在代码执行中,对变量赋值或者执行函数。

如下所示,以上述代码为例,GEC被放到ECS中,会将全局定义的变量、函数加入到GO,此时不进行赋值。我们可以看到此时的num1是一个undefined,test赋值了1个函数对象的指针。

以上述代码为例,在代码执行中,对变量赋值,如下所示。我们可以看到num1赋值成了1。

函数执行

那函数如何执行呢?如果执行过程中,执行到了1个函数,就会根据函数创建一个函数执行上下文(Functional Execution Context简称FEC) 并压入到执行上下文栈中。

FEC包含三部分内容:

  1. 在解析函数成为AST树结构时,会创建一个Activation Object(AO):AO中包含形参、arguments、函数定义和指向函数对象、定义的变量;
  2. 作用域链:由VO(在函数中就是AO对象)和父级VO组成,查找时会一层层查找;
  3. this绑定的值。

如下所示,以上述代码为例,FEC被放到了ECS中,解析函数成为AST树结构时会生成一个AO,AO中包含了函数的形参、arguments、函数定义以及定义的变量等等。

当FEC执行代码后,会对AO中的变量进行赋值,类似于上述GEC被放到ECS的操作。

关于VO

什么是VO呢?

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

但是在最新的ECMA规范中,VO变成了VE(VariableEnvironment即:变量环境),每一个执行上下文会关联到一个变量环境中。

代码分析

js 复制代码
var n = 100
function foo() {
    n = 200
}
foo()
console.log(n)

如上所示,首先会创建一个全局对象(GO) ,然后 执行栈(ECS) 会去执行全局的代码,这时候会生成一个全局执行上下文(GEC),执行前会把变量n和函数foo加入到GO中,执行时会进行赋值。

当执行到函数调用时,会根据函数体创建一个FEC(函数执行上下文) 并压入到执行栈中,上述代码的foo函数中,首先会访问自身VE ,发现没有变量n,这时候会访问父级VE即(GEC的VE,也就是GO) ,发现存在变量n,这时候就会对n进行赋值操作,所以会打印200

js 复制代码
var a = 100
function foo() {
    console.log(a)
    return 
    var a = 100
}
foo()

在执行到foo函数的时候,会根据函数体创建一个FEC,压入到执行栈中。在解析函数成为AST树结构的时候会创建一个AO,AO中包含了定义的变量a,此时a是一个undefined,因为并没有执行赋值。

然后代码执行,首先执行了console.log(a),此时会先访问自身VE 中的变量a,此处的VE就是上述创建的AO,此时a是undefined,所以直接打印了undefined;然后执行return,函数执行完毕,后续的var a = 100只进行了变量提升,并没有对a进行赋值,因为没有执行此处代码。

js 复制代码
function foo() {
    console.log(n) // undefined
    var n = 200
    console.log(n) // 200
}

var n = 100
foo()

此处逻辑与上一个代码案例类似,按照上一个分析即可。

js 复制代码
var n = 100

function foo1() {
    console.log(n) // 100
}

function foo2() {
    var n = 200
    console.log(n) // 200
    foo1()
}

foo2()
console.log(n) // 100

如下图所示,foo2产生的FEC被执行的时候,foo1函数又被调用,然后foo1产生的FEC会被推入栈顶优先执行,而parents scopes是在执行前,即解析的时候就确定了,因此foo1的parents scopes是指向GEC的VE,即GO的,所以访问的是GO的变量n。

相关推荐
linweidong5 分钟前
在企业级应用中,你如何构建一个全面的前端测试策略,包括单元测试、集成测试、端到端测试
前端·selenium·单元测试·集成测试·前端面试·mocha·前端面经
满怀101524 分钟前
【HTML 全栈进阶】从语义化到现代 Web 开发实战
前端·html
东锋1.336 分钟前
前端动画库 Anime.js 的V4 版本,兼容 Vue、React
前端·javascript·vue.js
满怀10151 小时前
【Flask全栈开发指南】从零构建企业级Web应用
前端·python·flask·后端开发·全栈开发
小杨升级打怪中1 小时前
前端面经-webpack篇--定义、配置、构建流程、 Loader、Tree Shaking、懒加载与预加载、代码分割、 Plugin 机制
前端·webpack·node.js
Yvonne爱编码1 小时前
CSS- 4.4 固定定位(fixed)& 咖啡售卖官网实例
前端·css·html·状态模式·hbuilder
SuperherRo2 小时前
Web开发-JavaEE应用&SpringBoot栈&SnakeYaml反序列化链&JAR&WAR&构建打包
前端·java-ee·jar·反序列化·war·snakeyaml
大帅不是我2 小时前
Python多进程编程执行任务
java·前端·python
前端怎么个事2 小时前
框架的源码理解——V3中的ref和reactive
前端·javascript·vue.js
Ciito2 小时前
将 Element UI 表格元素导出为 Excel 文件(处理了多级表头和固定列导出的问题)
前端·vue.js·elementui·excel