执行上下文栈

117 阅读11分钟

执行上下文栈

执行上下文栈反映了JavaScript代码的运行细则,了解执行上下文栈可以对代码的运行有更加深刻的了解代码的运行逻辑,和对变量空间(作用域链的了解)。本文不涉及ES3的执行上下文栈(VO,AO,GO等),主要讲解ES6的执行上下文栈(环境记录器版本)。

环境记录器包含(了解):

  • 全局环境记录(Global Environment Record 就是window或global等全局对象的记录;

  • 对象式环境记录(Object Environment Record) 就是对象的记录;

  • 声明式环境记录(Declarative Environment Record) 就是对let,const,var,function 声明的资源进行记录;

上下文

上下文一般的分为3种:

  • 全局上下文

  • 函数上下文

  • eval上下文(暂不考虑)

构成:

上下文由3部分构成:

  • this 对象

我们经常所说的this指向其实就是当前上下文对象中的this对象是什么,在当前上下文中所有this的使用都是使用当前上下文对象中的this。

  • 词法环境

词法环境,由两部分组成环境记录器(EnvironmenRecord)和外部可访问环境指针(outer)。

  • 变量环境

变量环境,由两部分组成环境记录器(EnvironmenRecord)和外部可访问环境指针(outer)。与词法环境略有不同,后续会单独讲解。

全局上下文

/** 全局执行上下文 */
GlobalExecutionContext = {
  /**this 指向 */
  ThisBinding: GlobalObject,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: null
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: null
  },
}

函数上下文

/** 函数执行上下文 */
FunctionExecutionContext = {
  /**this 指向 */
  ThisBinding: FunctionThis,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: OuterLexicalEnvironment
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: OuterVariableEnvironment
  },
}

词法环境

是什么

词法环境是用来记录当前所执行的代码上下文的词法作用域,即记录了所有function letconst 声明的变量。

组成

由两部分组成:

环境记录器(EnvironmentRecord) + 外部词法环境(outer)

环境记录器: 用来记录当前上下文中function letconst 声明的变量。

外部词法环境:用来指向上层上下文的词法环境对象,函数在哪个上下文的词法环境或变量环境内,那么他的词法环境和变量环境的外部变量环境(outer)就是那个上下文的词法环境和变量环境;简单地说,函数上下文的outer是函数所在词法环境或变量环境的上下文的词法环境和变量环境。

变量环境

是什么

变量环境使用来记录当前所执行的上下文变量作用域,即记录了所有 var 声明的变量。

组成

环境记录器(EnvironmentRecord)+ 外部变量环境(outer)

环境记录器: 用来记录当前上下文中var 生命的变量。

外部变量环境:用来指向上层上下文的变量环境对象,函数在哪个上下文的词法环境或变量环境内,那么他的词法环境和变量环境的外部变量环境(outer)就是那个上下文的词法环境和变量环境;简单地说,函数上下文的outer是函数所在词法环境或变量环境的上下文的词法环境和变量环境。

是什么

栈是一种常见的数据结构,主要特点是先进后出,后进先出。

样例代码

var num = 1
var str = "str"
let charStr = "!"
const num1 = 2
function bar() { 
  console.log("bar");
}
function foo() { 
  var num = 3
  console.log(num, str)
  function insertFoo() { 
    console.log('insertFoo');
    bar()
  }
  insertFoo()
}
function closePackage() { 
  var hello = "hello"
  return function () { 
    const instance = "JavaScript"
    return hello + " " + instance + charStr;
  }
}
foo()
var hello = closePackage()
hello()

在执行代码之前需要了解变量提升和函数提升两个概念

  • 变量提升

    • 变量提升是指 var 声明的变量会将的变量提升到作用域的头部,但不包括赋值,真正执行到该行代码才会进行赋值。
  • 函数提升

    • 函数提升是指 function 声明的函数提升到作用域头部,包括声明和赋值。

代码执行

这里假设是浏览器环境

1. 首先进入全局上下文,初始化上下文,并入栈
  • 上下文
/** 全局执行上下文 */
GlobalExecutionContext = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: null
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: null
  },
}

1.png

2. 进行变量提升和函数提升
  • 代码示意图

2.png

  • 上下文
/** 全局执行上下文 */
GlobalExecutionContext = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        bar: Function bar,
        foo: Function foo,
        closePackage: Function closePackage
    },
    /**外部环境指针 */
    outer: null
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        num: undefined,
        str: undefined,
        hello:undefined
    },
    /**外部环境指针 */
    outer: null
  },
}

3.png

3. 执行全局上下文代码
  • 代码示意图

4.png

  • 上下文
/** 全局执行上下文 */
GlobalExecutionContext = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        bar: Function bar,
        foo: Function foo,
        closePackage: Function closePackage,
        charStr: Let "!",
        num1: Const 2,
    },
    /**外部环境指针 */
    outer: null
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        num: 1,
        str: "str",
        hello:undefined
    },
    /**外部环境指针 */
    outer: null
  },
}

5.png

4. 进入 foo 函数上下文,初始化 foo 上下文并入栈
  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(Foo) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: GlobalExecutionContext[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: GlobalExecutionContext[VariableEnvironment]
  },
}

6.png

5. 在foo 上下文中进行变量提升和函数提升
  • 代码示例

7.png

  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(Foo) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        insertFoo: Function insertFoo
},
    /**外部环境指针 */
    outer: GlobalExecutionContext[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        num: undefined
    },
    /**外部环境指针 */
    outer: GlobalExecutionContext[VariableEnvironment]
  },
}

8.png

6. 执行foo 上下文代码
  • 代码

9.png

  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(Foo) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        insertFoo: Function insertFoo
},
    /**外部环境指针 */
    outer: GlobalExecutionContext[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        num: 3
    },
    /**外部环境指针 */
    outer: GlobalExecutionContext[VariableEnvironment]
  },
}

10.png

7. 进入 insertFoo 函数上下文,初始化 insertFoo 上下文并入栈
  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(insertFoo) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: FunctionExecutionContext(Foo)[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: FunctionExecutionContext(Foo)[VariableEnvironment]
  },
}

11.png

8. 执行 insertFoo 代码
  • 代码

12.png

  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(insertFoo) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: FunctionExecutionContext(Foo)[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: FunctionExecutionContext(Foo)[VariableEnvironment]
  },
}

13.png

9. 进入 bar 函数上下文,初始化 bar 上下文并入栈
  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(bar) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        insertFoo: Function insertFoo
},
    /**外部环境指针 */
    outer: GlobalExecutionContext[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        num: undefined
    },
    /**外部环境指针 */
    outer: GlobalExecutionContext[VariableEnvironment]
  },
}

14.png

10. 执行 bar 上下文代码
  • 代码

15.png

  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(bar) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        insertFoo: Function insertFoo
},
    /**外部环境指针 */
    outer: GlobalExecutionContext[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        num: undefined
    },
    /**外部环境指针 */
    outer: GlobalExecutionContext[VariableEnvironment]
  },
}

16.png

11. bar 上下文执行完毕出栈

17.png

12. 继续执行 insertFoo 上下文代码
  • 代码

18.png

  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(insertFoo) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: FunctionExecutionContext(Foo)[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: FunctionExecutionContext(Foo)[VariableEnvironment]
  },
}

19.png

13. insertFoo 上下文执行完毕出栈

20.png

14. 继续执行foo上下文代码
  • 代码

21.png

  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(Foo) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        insertFoo: Function insertFoo
},
    /**外部环境指针 */
    outer: GlobalExecutionContext[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        num: 3
    },
    /**外部环境指针 */
    outer: GlobalExecutionContext[VariableEnvironment]
  },
}

22.png

15. foo 上下文代码执行完毕出栈

23.png

16. 继续执行全局上下文
  • 代码

24.png

  • 上下文
/** 全局执行上下文 */
GlobalExecutionContext = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        bar: Function bar,
        foo: Function foo,
        closePackage: Function closePackage,
        charStr: Let "!",
        num1: Const 2,
    },
    /**外部环境指针 */
    outer: null
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        num: 1,
        str: "str",
        hello:undefined
    },
    /**外部环境指针 */
    outer: null
  },
}

25.png

17. 全局上下文执行下一行代码
  • 代码

26.png

18. 进入 closePackage 函数上下文,初始化上下文
  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(closePackage) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: GlobalExecutionContext[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {},
    /**外部环境指针 */
    outer: GlobalExecutionContext[VariableEnvironment]
  },
}

27.png

19. 在clsoePackage上下文中进行变量提升和函数提升
  • 代码

28.png

  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(closePackage) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        匿名函数1: function
    },
    /**外部环境指针 */
    outer: GlobalExecutionContext[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        hello: undefined
    },
    /**外部环境指针 */
    outer: GlobalExecutionContext[VariableEnvironment]
  },
}

29.png

20. 继续执行closePackage上下文
  • 代码

30.png

这里返回的匿名函数1,仅仅是匿名函数地址,匿名函数真正存在于closePackage的词法环境内。

  • 上下文

31.png

21. closePackage 执行完毕,返回匿名函数1地址,出栈

32.png

22. 继续执行全局上下文
  • 代码

33.png

  • 上下文
/** 全局执行上下文 */
GlobalExecutionContext = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        bar: Function bar,
        foo: Function foo,
        closePackage: Function closePackage,
        charStr: Let "!",
        num1: Const 2,
    },
    /**外部环境指针 */
    outer: null
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        num: 1,
        str: "str",
        hello: 匿名函数1地址
    },
    /**外部环境指针 */
    outer: null
  },
}

34.png

23. 找到匿名函数1,初始化匿名函数1上下文

在文章开头有提到outer是,函数在哪个上下文的词法环境或变量环境内,那么他的词法环境和变量环境的外部变量环境(outer)就是那个上下文的词法环境和变量环境;简单地说,函数上下文的outer是函数所在词法环境或变量环境的上下文的词法环境和变量环境。

这里匿名函数1 的地址在全局上下文中,但是匿名函数1 本身在 closePackage 的词法作用域中,那么他的 outer 就是 closePackage 的词法环境和变量环境。

  • 这个就是闭包的工作原理,可以访问到被定义时的上下文的词法环境和变量环境。
  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(匿名函数1) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {

    },
    /**外部环境指针 */
    outer: FunctionExecutionContext(closePackage)[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
    },
    /**外部环境指针 */
    outer: FunctionExecutionContext(closePackage)[VariableEnvironment]
  },
}

35.png

这里可以看出虽然 closePackage 的上下文不在上下文执行栈中,但是匿名函数1,仍然可以访问到 closePackage 的词法环境和变量环境,那根据垃圾回收机制,引用了就不能被回收,closePackage 的词法环境和变量环境就混存在于内存当中不会被回收,除非 匿名函数1 被回收。

这个就是闭包可能带来的内存泄漏,和延长变量声明周期的原理

24. 执行匿名函数1上下文代码
  • 代码

36.png

  • 上下文
/** 函数执行上下文 */
FunctionExecutionContext(匿名函数1) = {
  /**this 指向 */
  ThisBinding: window,
  /**词法环境 */
  LexicalEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
        instance:C("JavaScript")
    },
    /**外部环境指针 */
    outer: FunctionExecutionContext(closePackage)[LexicalEnvironment]
  },
  /**变量环境 */
  VariableEnvironment: {
    /**环境记录器 */
    EnvironmentRecord: {
    },
    /**外部环境指针 */
    outer: FunctionExecutionContext(closePackage)[VariableEnvironment]
  },
}

37.png

25. 匿名函数1执行完毕,返回 "hello JasaScript!",出栈

38.png

26. 进入全局上下文执行代码
  • 代码

39.png

40.png

27. 全局上下文执行完毕出栈,js运行完成

41.png

总结

执行上下文栈让我们知道了代码是如何执行的,其中涉及了作用域链、闭包、垃圾回收机制和this指向的相关知识,立即执行上下文栈可以使我们更好的理解作用域链、闭包、垃圾回收机制。

补充

作用域链

作用域链其实就是我们找变量的过程。

  1. 在当前上下文的变量环境和词法环境找,若找到则结束。

  2. 若找不到在outer的上下文中的变量环境和词法环境找,若找到结束。

  3. 若找不到重复步骤2,直到全局上下文。

this

this指向问题也很简单,首先需要知道基础规则。

this 只存在于函数(非箭头函数)和全局中

this通常指向调用的实例,exp: obj.foo() foo 的 this就是 obj(注:如果是 foo() 一般指向全局对象)

使用 new 操作符调用函数 this 会指向空对象 {}

call apply bind 可以去定义this 的指向用户定义

获取this指向后 初始化上下文的时候就会把this 赋值到获取的this上,整个上下文的this都从上下文取。