likes
comments
collection
share

JS高级 - JS执行过程

作者站长头像
站长
· 阅读数 32

初始化全局对象

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

  • 对象 所有的作用域(scope)都可以访问
  • 该对象中存在全局的属性和方法,如Date、Array、String、Number、setTimeout、setInterval等
  • 这个GO对象其实就是globalThis对象
  • 在globalThis中有globalThis属性,指向自己

JS高级 - JS执行过程

执行上下文

js引擎内部有一个执行上下文栈(Execution Context Stack,简称ECS),它是用于执行代码的调用栈

任意一段代码在被执行之前都会创建对应的执行上下文(Execution Contexts), 本被压入ECS后进行执行,执行完成后再出栈

所以在ECS中,栈顶的那个EC一定是正在被执行的那个活跃执行上下文

同样,全局的代码块在执行的时候会构建一个 Global Execution Context(GEC) 被压入ECS中

GEC往往都是栈底的EC,因为它是被最早创建的,也是最早被压入ECS的,

因此当GEC出栈的时候,就意味着JS代码执行完毕

对于任意一个EC,其内部都会初始化三个属性

  1. 作用域链(scope chain)
  2. VO (Variable Object)
  3. this

在GEC中VO对应的就是GO对象,所以使用var声明的变量最终会被挂载到globalThis上

全局代码在被执行的时候,一定会创建GEC,所以全局存在this,且其值为globalThis对象

所以箭头函数在按照作用域链查找this值的时候,最后一定会指向globalThis

通过JS代码创建对应EC是在parser阶段进行的,也就是在转换成对应的AST树的时候完成

在此过程中,会将定义的变量、函数等加入到VO中

  • 函数是最先被解析的,在此阶段对应的函数对象会被创建,且VO中存在与函数名同名变量指向该函数对象
  • 函数被解析后,会提前收集到所有的变量定义,并将他们挂载到VO对象上,对应的属性值皆被初始化为undefined
  • 这个过程被称之为变量的作用域提升(hoisting) 或被称之为预解析
var foo = 'foo'

function foo() {
  console.log('function foo')
}

// 函数的预解析是早于变量的预解析
// 所以在本例中,foo变量最早指向的是foo函数对象, 随后又被赋值为了字符串foo
// 因此输出字符串foo
console.log(foo) // => foo

此时,在代码真正被执行前,其内存图类似如下:

JS高级 - JS执行过程

EC创建被初始化完成后,才会真正逐行执行对应的JS代码

JS高级 - JS执行过程

函数的执行

在执行的过程中执行到一个函数时,就会根据函数体创建一个函数执行上下文(Functional Execution Context,简称FEC),并且压入到EC Stack中

因为每一个EC都需要一个VO对象,用于进行变量和函数的初始化

所以当进入一个函数执行上下文时,会创建一个AO对象(Activation Object)

这个AO对象会使用arguments作为初始化,并且初始值是传入的参数

这个AO对象会作为执行上下文的VO来存放变量的初始化,包括函数内部定义的变量和函数,也包裹定义的形参也会挂载到AO对象上

作用域链

当进入到一个执行上下文时,执行上下文也会关联一个作用域链(Scope Chain)

  • 作用域链是一个对象列表,用于变量标识符的求值,也就是用于变量标识符值的查找

  • 当进入一个执行上下文时,这个作用域链被创建,并且根据代码类型(这里的类型指的是全局作用域或函数作用域等),添加一系列的对象

  • 和this不同,作用域是在函数被定义的那一刻就已经被确定下来,保存在函数内部一个私有属性[[scopes]]

    也就是说JS的作用域是词法作用域,而不是动态作用域

  • [[scopes]]属性本质是一个伪数组对象

  • 对于全局代码,其作用域链中只有一个值,也就是GO

  • 对于函数执行上下文,其EC中对应的scope chain会使用函数对象的[[scopes]]属性

  • 在查找变量的时候,会优先在VO中进行查找,如果没有会去scope chain中依次进行查找,这个变量的查找规则就被称之为作用域链

var n = 100

function fun() {
  // return 是运行时行为
  // VO的创建是编译时行为
  console.log(n) // => undefined
  return
  var n = 200
}

fun()
function foo() {
  var bar = baz = 100
  /*
    上述代码等价于
    baz = 100
    var bar = 100

    所以bar是局部变量
    而baz是隐式全局变量
  */
}

foo()

console.log(baz) // => 100
console.log(bar) // error
转载自:https://juejin.cn/post/7107617044836827144
评论
请登录