深入理解JavaScript执行上下文

在《深入理解JavaScript执行上下文栈》这篇文章中,我们已经介绍了执行上下文相关概念:

执行上下文

分类:全局上下文、函数上下文

全局上下文:执行全局代码时,创建全局上下文。
函数上下文:执行函数时,创建函数上下文。

主要属性:

  • 变量对象(Variable object,VO)
  • 作用域链(Scope chain)
  • this

这一篇我们将介绍执行上下文的具体处理过程。

示例

我们来看一个经常在面试中被问到的问题:

var scope = 'global scope'
function checkscope(){
    var scope = 'local scope'
    function f(){
        return scope
    }
    return f()
}
checkscope()
var scope = 'global scope'
function checkscope(){
    var scope = 'local scope'
    function f(){
        return scope
    }
    return f
}
checkscope()()

我们发现这两段代码最后输出的都是’local scope’,那么这两段代码有什么不同呢?

具体分析

我们结合前面所学的执行上下文栈、变量对象、作用域链相关知识来分析一下这两段代码的执行过程。

首先我们先来看第一段代码:

var scope = 'global scope'
function checkscope(){
    var scope = 'local scope'
    function f(){
        return scope
    }
    return f()
}
checkscope()

1.在程序初始化阶段,创建全局执行上下文,全局执行上下文入栈:

ECStack = [
    globalContext
] 

2.初始化全局上下文

globalContext = {
    VO: [global],
    Scope: [globalContext.VO]
    this: globalContext.VO
}

2.初始化全局上下文的同时,checkscope函数被创建,保存父级作用域链到函数的[[scope]]属性

checkscope.[[scope]] = [
    globalContext.VO
]

3.checkscope函数预编译,创建checkscope函数执行上下文,checkscope函数执行上下文进入执行上下文栈

ECStack = [
    checkscopeContext,
    globalContext
]

4.初始化checkscope函数执行上下文:

  1. 复制[[scope]]属性创建作用域链
  2. 用arguments创建变量对象
  3. 初始化变量对象,即加入形参,函数声明,变量声明
  4. 将变量对象压入checkscope作用域链顶端
checkscopeContext = {
     AO: {
        arguments: {
                length: 0
        },
        scope: undefined,
        f: reference to function f(){}
    },
    Scope: [AO, globalContext.VO],
    this: undefined
}

4.初始化checkscope函数执行上下文的同时,f函数被创建,保存f函数的父级作用域链到函数的[[scope]]属性

f.[[scope]] = [
    checkscopeContext.AO,
    globalContext.VO
]

5.执行checkscope函数

checkscopeContext = {
    AO: {
        arguments: {
                length: 0
        },
        scope: 'local scope',
        f: reference to function f(){}
    },
    Scope: [AO, globalContext.VO],
    this: undefined
}

6.f函数预编译,创建f函数执行上下文,将f函数执行上下文压入执行上下文栈

ECStack = [
    fContext,
    checkscopeContext,
    globalContext
]

7.初始化f函数执行上下文

  1. 复制[[scope]]属性创建作用域链
  2. 用arguments创建变量对象
  3. 初始化变量对象,即加入形参,函数声明,变量声明
  4. 将变量对象压入checkscope作用域链顶端
fContext = {
    AO: {
        arguments: {
                length: 0
        }
    },
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
    this: undefined
}

8.执行f函数沿着作用域链查找 scope 值,返回 scope 值

9.f函数执行完毕,f函数执行上下文从执行上下文栈弹出

ECStack = [
    checkscopeContext,
    globalContext
]

10.checkscope 函数执行完毕,checkscope 函数执行上下文从执行上下文栈中弹出

ECStack = [
    globalContext
]

既然两段代码输出的都是local scope,那么究竟是哪里不同呢?


我们接着看第二段代码:

var scope = 'global scope'
function checkscope(){
    var scope = 'local scope'
    function f(){
        return scope
    }
    return f
}
checkscope()()

执行过程1-5参考第一段代码,两段代码不同的是在过程5之后

6.checkscope函数执行完毕,返回函数f并从执行上下文栈中弹出

ECStack = [
    globalContext
]

7.f函数预编译,创建f函数执行上下文,将f函数执行上下文压入执行上下文栈

ECStack = [
    fContext,
    globalContext
]

8.初始化f函数执行上下文

  1. 复制[[scope]]属性创建作用域链
  2. 用arguments创建变量对象
  3. 初始化变量对象,即加入形参,函数声明,变量声明
  4. 将变量对象压入checkscope作用域链顶端
fContext = {
    AO: {
        arguments: {
                length: 0
        }
    },
    Scope: [AO, checkscopeContext.AO, globalContext.VO],
    this: undefined
}

9.执行f函数沿着作用域链查找 scope 值,返回 scope 值

10.f函数执行完毕,f函数执行上下文从执行上下文栈弹出

ECStack = [
    globalContext
]

两段代码我们已经分析完了,我们发现两段代码虽然结果相同但是执行上下文的出栈顺序是不同的,实际上我们平常开发中就是使用类似第二段代码的执行顺序来实现闭包的。

你可能感兴趣的:(深入理解JavaScript执行上下文)