JavaScript ES3中的执行上下文是怎样的 II

186 阅读2分钟

「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战

前言

每一个函数都有属于自己的执行上下文(execution context)而每个函数上下文中又包含了作用域(scope)。这些相关内容可以在昨天的文章JavaScript ES3中的执行上下文是怎样的 I 有详细的介绍。那么问题来了多个函数直接又是怎样关联到一起的呢?这就引出了一个新的概念作用域链(scope chain)。

作用域链(scope chain)

函数运行时为了能保证函数中的变量对象(variable object)能够进行有序访问。每当我们在使用一个变量时会先从当前上下文(context)中去查找,找到的话则停止没有的会沿着作用域链向上查找。如果都没找到的话才能全局上下文(globalContext)中去查找。

let n = 1
function fun1(){
    function fun2(){
        console.log(n)
    }
    fun2()
}
fun()

上述代码执行过程大致如下:

  • 我们在使用变量n时,首先在函数fun2(functionContextfun2)中去查找
  • 没找到则会沿着作用域链(scope chain)中去查找
  • 沿着作用域链(scope chain)来到函数fun1(functionContextfun1)
  • 还是没有找到,此时作用域()链(scope chain)已经到头了。
  • 则来的全局作用域(globalContext)中查找,最终在全局作用域(globalContext)中找到了变量n结束。

作用域链的形成?

当我创建一个函数时,函数的内部属性[[scope]]会保存全局上下文的变量对象(variable object)以及如果有函数上下文(functionContext)的话还会保存的活动变量(VO)。

大致情况如下:

fun1.[[scope]] = [
    globalContext.VO
]
fun2.[[scope]] = [
    globalContext.VO,
    functionContextfun1.AO
]

模拟执行过程

var a = 1
function fun1(b){
    var c = 1
    console.log(a,b,c,arguments)
}
fun1(1,2,3)

image.png

我们对上述代码模拟执行过程:

1.将全局作用域入栈到执行上下文栈中。

var ECStack = [globalContext];
  1. 创建函数fun1时,内部属性[[scope]]会保存全局上下文(globalContext)的变量对象(VO)。
var fun1Scope.[[scope]] = [
    globalContext.VO
];
  1. 创建函数fun1的函数上下文(functionContextFun1),确认作用域链
var functionContextFun1 = {
    Scope: fun1Scope.[[scope]],
}
  1. 创建函数上下文(functionContextFun1)的活动变量
var functionContextFun1 = {
    Scope: fun1Scope.[[scope]],
    AO:{
        arguments: [],
        c:undefined
    }
}
  1. 将活动变量unshift到当前作用域(Scope)中
var functionContextFun1 = {
    Scope: [AO,...fun1Scope.[[scope]]],
    AO:{
        arguments: [],
        c:undefined
    }
}
  1. 进入执行阶段,将创建好的functionContextFun1入栈
var functionContextFun1 = {
    Scope: [AO,...fun1Scope.[[scope]]],
    AO:{
        arguments: [],
        c:undefined
    }
}
var ECStack = [ functionContextFun1, globalContext ];
  1. 执行函数fun1,并对活动变量进行赋值
var functionContextFun1 = {
    Scope: [AO,...fun1Scope.[[scope]]],
    AO:{
        arguments: [1,2,3],
        c:1
    }
}
var ECStack = [ functionContextFun1, globalContext ];
  1. 当执行到console.log方法时,会对使用到的变量进行查找;在函数内部找到变量c,从形参中找到变量b,从全局作用域中找到c。执行完毕后将functionContextFun1进行出栈。
var ECStack = [globalContext];