作用域、执行上下文和调用栈

执行上下文

执行上下文(Execution Context)是一段代码执行时的环境,每个函数都有它自己的执行上下文。
这个环境里面大概包含:

  • 传入函数的参数(arguments)
  • 函数的作用域链
  • this 的值

调用栈

调用栈(call stack)就是执行上下文栈(execution context stack),用来记录当前正在执行的函数

  • 执行一个函数 -> 加入调用栈
  • 函数结束 -> 弹出调用栈
  • 调用栈的顶部就是当前正在执行的函数

作用域

作用域(scope)指在一个函数的 执行上下文 中能够访问的变量的集合

A scope is just a finite set of variables/objects that an execution context have access to

  • 每个函数有自己的作用域,除此之外还有全局作用域,ES6 又有了块级作用域
  • 每个函数都有权访问外层函数的作用域和全局作用域,这些作用域加起来构成了一个作用域链
  • JS 的作用域是词法作用域(对比动态作用域),是在编译时就确定的

浏览器 和 Node 的实现

image.png
可以看到左侧 VARIABLES 栏就是作用域,当前代码执行到 a 函数,此时有局部(函数)作用域:b + foo + this,但是注意下面的 WATCH,此时是访问不到 bar 的。
这是因为在编译时 JS 引擎并不会把外层的作用域一股脑儿地给你,而是通过闭包的形式让你访问,而闭包只有在内部函数引用了外部变量的情况下产生。在 a 函数里并没有引用全局变量 bar 或者 foo,所以没有产生闭包。

把下面一行注释取消后,发现产生了一个 Closure 对象,包含变量 bar:
image.png
执行到函数 c 时,可以看到作用域链为:Local:c + Closure(b) + Closure(global) + Global
没有 Closure(a) 是因为没有地方引用 a 中的变量
image.png
再看看 Chrome 中的作用域:
image.png
与 Node 环境不同的是,全局变量并不会产生闭包,所以不需要在函数中引用也能访问。
另外,可以看到 Scope 栏的 Script 对象,只有 bar 属性,因为另外两个 var 定义的全局变量在 window 上。

参考

Javascript Scope Chain and Execution Context simplified | by Bilal Alam | KoderLabs | Medium
Is “Call stack” the same as “Execution context stack” in JavaScript? - Stack Overflow