什么是执行上下文

执行上下文的概念描述了代码内部是怎么运行的。JavaScript 执行上下文是指允许 JavaScript 代码执行的环境。。变量或函数的上下文决定了它们可以访问那些数据,以及它们的行为。可以把执行上下文想象成一个盒子,盒子内部是我们的代码。
简而言之,执行上下文 (EC) 是 JavaScript 代码运行的环境。所谓环境,是指 JavaScript 代码可以访问的 this、变量、对象和函数的当前值

执行上下文类型

在JavaScript中有三种不同的执行上下文。

1.全局上下文

是最外层的上下文,根据ECMAScript实现的宿主环境,表示全局上下文的随想可能不一样。在浏览器中全局上下文就是我们说的window对象。
window对象是指或者是连接到全局对象,通常在JS引擎进入全局执行环境前就已创建,包含了了比如localStroage,innerWidth,和事件处理等等属性。
this对象(在全局执行上下文)是一个对象指向window对象。

2.函数上下文

当一个函数被执行或者调用时,将创建一个新的执行上下文。当一个JS引擎看见一个函数被执行,它会为这个函数创建一个局部上下文,默认包含argumentd对象和this对象。
argumnets对象内部是键值对形式存储,并且有一个属性length,表示有多少个参数。当参数为空时length默认为0。
this对象取决于函数是怎么被调用的,如果是通过对象调用,那么this指向那个对象,否则this执行window对象或者“undefined”。

3.eval函数上下文

执行栈

栈是一种后进先出的数据结构,另一方面,JavaScript 执行堆栈是跟踪在脚本生命周期中创建的所有执行上下文的堆栈。
全局执行上下文是默认存在并且在执行栈的最底部,当开始执行全局上下文中的代码,如果JS引擎法相有函数调用,他会为这个函数创建一个函数执行上下文并将它推入执行栈最上方。
JS引擎总是执行在执行栈最上方的执行上下文。当一个执行上下文种的所有代码执行完毕,执行栈会将该执行上下文弹出,接着执行下一上下文,然后如此循环。

创建执行上下文

创建执行上下文分为两步

  1. 创建阶段
  2. 执行阶段

    创建阶段

    两种词法环境在创建阶段创建分别是
  • Lexical Environment:词法环境组件是一种基于 JavaScript 代码的词法嵌套结构的结构,它定义了标识符与变量和函数值的关联。
  • Variable Environment:只定义了标识符和变量之间的关系。

两个之间的不同在于,Lexical Environment存储的是标识符绑定到变量(const和let)和函数,然而Variable Environment简单的存储了标识符绑定到变量(var)。

Environment Record

在每个词法环境种都有一个Environment Record,在词法环境种建立的标识符便规定记录在Environment Record种。
每次评估 JavaScript 代码(var/func 声明或赋值)时都会创建一个新的环境记录,以记录由该代码创建的标识符绑定,在JavaScript种这种环境指作用域。
每个环境记录的 [[OuterEnv]] 字段要么是空的,要么是对外部环境记录的引用。外部环境对象是子函数可以访问父作用域的原因。
当IS引擎在函数内遇到一个变量,他会在当前Environment Record种寻找,如果找不到,他会搜索外部作用域(父Environment Record)再找到之前一直往外找,一直到全局作用域。这个查找过程叫做作用域链。
对于Environment Record有如下几种类型

  • **Declarative Environment Record顾名思义,变量、类、模块和/或函数声明都存储在这里。由包含在声明性环境记录范围内的声明定义的标识符集合是绑定的。declarative Environment Record绑定了在作用域中的声明集合。
  • Object Environment Record
    所有全局作用域中内建的绑定,指向全局对象的window对象是这一种,全局变量和函数被添加到环境中,这也是为什么window.localStorage和window.var的名称可以直接访问。arguments 对象和this对象构成了本地/函数执行上下文中的object Environment Record。
  • Global Environment Record
    尽管全局环境记录理论上是单个记录,但它被描述为封装object 和declarative Environment Record的组合。它没有外部环境,因为它是 [[OuterEnv]] 为空。用于全局声明。

示例代码创建过程

  1. var name = "Joe";
  2. let input = "Hey!!!";
  3. function broadcast(message) {
  4. return `${name} says ${message}`;
  5. }
  6. console.log(broadcast(input));
  1. 创建一个全局执行上下文并将其压入执行栈。
  2. 创建window和全局对象间的绑定。
  3. this指向window对象。this的绑定根据函数怎么被调用或严格模式是否开启而不同。
  4. 在全局内存中创建**name**标识符,并将其赋值为undefined,这个过程叫做提升(Hoisting
  5. 创建input标识符,没有初始值。
  6. 创建broadcast标识符并将整个函数存储,这叫做函数提升。

    执行阶段

  7. 拿到name的值并将它和存储中的标识符绑定。

  8. 拿到input的值并将它和存储中的标识符绑定。
  9. 当遇到console.log,立即评估参数。
  10. 当遇到broadcast函数执行,构建一个函数上下文并将其压入执行栈。
  11. broadcast函数执行上下文开始创建阶段。
  12. 创建_**arguments**_对象,length为0。
  13. 在参数对象的第一个索引处,附加提供的参数**message**
  14. 创建一个标识符message并将赋予函数调用参数的值存储在函数的本地内存中。
  15. 执行return语句。
  16. 当遇到第一个变量name,先在函数内部寻找。
  17. 在内部找不到,接着到父级作用域寻找。
  18. 找到后将变量替换为值。
  19. 在内部查找message变量。
  20. 找到,替换掉变量。
  21. 执行结束,弹出函数执行上下文。
  22. 全局上下文接着执行。
  23. 打印出,Joe says “Hey!!!”。
  24. 全部执行结束。
    1. GlobalExecutionContext = {
    2. LexicalEnvironment: {
    3. EnvironmentRecord: {
    4. DeclarativeEnvironmentRecord: {
    5. input: "Hey!!!",
    6. broadcast: {
    7. LocalExecutionContext: {
    8. LexicalEnvironment: {
    9. EnvironmentRecord: {
    10. DeclarativeEnvironmentRecord: {
    11. message: "Hey!!!",
    12. },
    13. ObjectEnvironmentRecord: {
    14. arguments: {
    15. 0: message,
    16. length: 1
    17. }
    18. this: < ref.to window obj. >
    19. },
    20. },
    21. OuterEnv: < ref.to LexicalEnvironment of the GlobalExecutionContext > ,
    22. },
    23. },
    24. },
    25. },
    26. ObjectEnvironmentRecord: {
    27. window: < ref.to Global obj. > ,
    28. this: < ref.to window Obj. > ,
    29. },
    30. OuterEnv: < null > ,
    31. },
    32. },
    33. VariableEnvironment: {
    34. EnvironmentRecord: {
    35. DeclarativeEnvironmentRecord: {
    36. name: "Joe"
    37. },
    38. },
    39. },
    40. }