JavaScript中执行上下文和执行栈

本文将深入探讨JAVAScript中最重要的基础知识之一:执行上下文 。通过对此篇文章的阅读,对以下几个方面的知识你将会有更加清晰的认识:

  • 解释器的执行机制
  • 为何函数和变量可以在声明前使用以及它们的值究竟是如何确定的
什么是执行上下文?
当代码在JS中运行时,代码的执行环境非常重要,JavaScript中可执行的代码分为以下几类:
  • 全局代码:代码首次执行时所进入的默认执行环境
  • 函数代码:函数体内的代码
  • Eval代码:eval内部的代码
我们可以在网上找到很多与作用域相关的文档等,本文为了便于知识点的理解,将执行上下文看作是当前代码执行所处的环境/作用域 。下面是一个包括全局和函数上下文的代码示例:
JavaScript中执行上下文和执行栈

文章插图
 
以上示例代码结构很简明,一个由紫色实线包裹的全局上下文和三个分别由绿色、蓝色和橙色实线包裹的函数上下文 。每个程序中只能有一个可被其他程序所访问的全局上下文 。函数上下文可以有任意多个,并且每个函数在调用的时候都会产生一个新的函数上下文和一个私有的作用域,当前作用域中所声明的任何变量都不能被外部所直接访问或调用 。上例中,函数可直接访问当前上下文外部声明的变量,但是外部函数上下文不能访问内部声明的变量或者函数 。为何会出现这种情况呢?代码到底是怎么执行的呢?
执行环境栈
浏览器中JavaScript解释器的运行是单线程的 。这也就意味着在浏览器中同一时刻只能做一件事情,其他行为或者事件需要在执行栈中排队等待 。下图是对单线程的抽象展示:
JavaScript中执行上下文和执行栈

文章插图
 
当浏览器首次加载脚本语言的时候,会默认进入全局执行上下文 。如果在全局代码中调用其他函数,当前程序的时序会自动进入所调用的函数中,与此同时会创建一个新的执行上下文并将其压入执行栈的顶部 。如果在当前函数内部调用其他函数,执行过程如上所述 。代码的执行流程会进入到内部函数中,创建一个新的执行上下文并将它压入执行栈的顶部 。浏览器永远执行位于栈顶的执行上下文,并且一旦当前函数执行上下文执行结束,它将从栈顶弹出,执行控制权也会回到当前栈的新栈顶 。这样,执行环境栈中的上下文就会被依次执行和弹出栈顶,直到回到全局上下文,下例所示:
(function foo(i){ if (i===3) { return; }else{ foo(++i); }}(0))复制代码代码自调用三次,i的值不断从1自增 。每次函数foo被调用的时候,一个新的执行上下文就会创建 。一旦当前上下文执行结束,它就会从栈顶弹出,回到栈顶的新的上下文,直到再次回到全局上下文 。
JavaScript中执行上下文和执行栈

文章插图
 
执行栈中需要记住的5个关键点:
  • 单线程
  • 同步执行
  • 唯一的一个全局上下文
  • 不限个数的函数上下文
  • 每个函数的调用都会产生一个新的执行上下文,即使是函数对自己的调用
详解执行上下文
截至目前我们已经知道每当一个函数被调用的时候,就会产生一个新的执行上下文 。但是,在JavaScript解释器中,对每个执行上下文的调用都分为以下两个阶段:
  • 创建阶段[当函数被调用,内部代码被执行之前的阶段]:
  • 创建作用域链
  • 创建变量、函数、参数
  • 确定this的值
  • 激活/代码执行阶段:
  • 确定函数的值和引用,然后执行代码
因为可以将执行上下文概念性的描述为含有三个属性的对象:
executionContextObj = { 'scopeChain':{/*variableObject+所有父类执行上下文的variableObject*/}, 'variableObject':{/*函数形参/实参,内部的变量和函数声明*/}, 'this':{}}复制代码激活/变量对象[AO/VO]
执行上下文对象是在函数被调用,但是在函数被执行前所产生的 。也就是上文所述的阶段1—创建阶段 。比部分中,解释器对执行上下文对象的创建主要是通过浏览函数的实参和形参、当前函数内部的变量声明和函数声明 。这部分的浏览结果会成为执行上下文对象中的变量对象 。
【JavaScript中执行上下文和执行栈】解释器对代码执行的伪逻辑概述: