执行上下文与作用域

271 阅读4分钟

1 执行上下文

变量或函数的上下文决定了它们可以访问哪些数据,以及它们的行为

每个上下文都有一个关联的变量对象(variable object),而这个上下文中定义的所有变量和函数都存在于这个对象上。虽然无法通过代码访问变量对象,但后台处理数据会用到它。

1.1 全局上下文

全局上下文是最外层的上下文。根据ECMAScript实现的宿主环境,表示全局上下文的对象可能不一样。 在浏览器中,全局上下文就是我们常说的window对象,因此所有通过var定义的全局变量和函数都会成为window对象的属性和方法。

var myprop = "hello world"
console.log(window.myprop);  // hello world
let myprop = "hello world"
console.log(window.myprop) // undefined

使用let和const的顶级声明不会定义在全局上下文中,但在作用域链解析上效果是一样的。

**上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数(全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器)。 **

1.2 函数上下文

每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个上下文栈上。在函数执行完之后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文。

1.3 作用域链

上下文中的代码在执行的时候,会创建变量对象的一个作用域链(scope chain)。这个作用域链决定了各级上下文中的代码在访问变量和函数时的顺序。

代码正在执行的上下文的变量对象始终位于作用域链的最前端。

如果上下文是函数,则其活动对象(activation object)用作变量对象。

活动对象最初只有一个定义变量:arguments。(全局上下文中没有这个变量。)作用域链中的下一个变量对象来自包含上下文,再下一个对象来自再下一个包含上下文。以此类推直至全局上下文;全局上下文的变量对象始终是作用域链的最后一个变量对象。

代码执行时的标识符解析是通过沿作用域链逐级搜索标识符名称完成的。搜索过程始终从作用域链的最前端开始,然后逐级往后,直到找到标识符。(如果没有找到标识符,那么通常会报错。)

var color = "blue";

function changeColor() {
    // 由于color是全局变量,在全局上下文中,
    // 可以在changeColor中访问
    if (color === "blue") {
      color = "red";
    } else {
      color = "blue";
    }
}

changeColor();

对这个例子而言,函数changeColor()的作用域链包含两个对象:一个是它自己的变量对象(就是定义arguments对象的那个),另一个是全局上下文的变量对象。这个函数内部之所以能够访问变量color,就是因为可以在作用域链中找到它。

此外,局部作用域中定义的变量可用于在局部上下文中替换全局变量:

var color = "blue";

function changeColor() {
  // anotherColor是一个局部变量
  // 在局部上下文中,所以在changeColor函数上下文中都可以访问
  let anotherColor = "red";

  function swapColors() {
    // 虽然swapColors是一个函数,但是定义在changeColor里面,
    // 所以可以可以访问anotherColor变量
    let tempColor = anotherColor;
    anotherColor = color;
    color = tempColor;

    //这里可以访问color、anotherColor和tempColor
  }

  //这里可以访问color和anotherColor,但访问不到tempColor
  swapColors();
}

//这里只能访问color
changeColor();

全局上下文、changeColor()的局部上下文和swapColors()的局部上下文。全局上下文中有一个变量color和一个函数changeColor()。changeColor()的局部上下文中有一个变量anotherColor和一个函数swapColors(),但在这里可以访问全局上下文中的变量color。

swapColors()的局部上下文中有一个变量tempColor,只能在这个上下文中访问到。全局上下文和changeColor()的局部上下文都无法访问到tempColor。而在swapColors()中则可以访问另外两个上下文中的变量,因为它们都是父上下文。

上面例子的作用链:

window
    - color
    - changeColor()
        - anotherColor
        - swapColors()
            - tempColor

内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中的任何东西

swapColors()局部上下文的作用域链中有3个对象:swapColors()的变量对象、changeColor()的变量对象和全局变量对象。swapColors()的局部上下文首先从自己的变量对象开始搜索变量和函数,搜不到就去搜索上一级变量对象。changeColor()上下文的作用域链中只有2个对象:它自己的变量对象和全局变量对象。因此,它不能访问swapColors()的上下文。