JavaScript 基础:执行环境、作用域链、变量提升

213 阅读4分钟

执行环境

执行环境是JS中重要的概念。执行环境定义了变量或函数有权访问其他数据,决定了它们各自的行为。每个环境都有一个与之相关联的变量对象,环境中定义的所以变量和函数都保存在这个对象中。

执行环境分为全局执行环境和局部执行环境。

  • 全局执行环境:在Web浏览器中,全局执行环境被认为是 window 对象,因为所有全局全局变量和函数都是作为 window 对象的属性和方法创建的。
  • 局部执行环境:每个函数都有自己的执行环境。当执行流进入一个函数时,函数的环境就会被推入一个环境栈中。而且在函数执行完毕后,栈将其环境弹出,并将控制权返还给之前的执行环境。

作用域链

当代码在一个环境中执行时,会创建变量对象的一个作用域链。作用域链的用途时保证对执行环境有权访问的所有变量和函数的有序访问。

  • 作用域链的前端,始终都是当前执行的代码所在环境的变量对象(环境变量对象),如果这个环境是函数(局部执行环境),则将其活动对象(activation object)作为变量对象(环境变量对象)。活动对象在最开始只包含一个变量,即 arguments 对象(这个变量在全局环境中是不存在的)。

  • 作用域链中的下一个环境变量对象来自包含(外部)环境,而下一个环境变量对象来自下一个包含环境。这样一直延续到全局执行环境。

  • 全局执行环境的变量对象始终都是作用域链中的最后一个对象。

作用域中的变量

查询标识符(变量名?)

使用 var 声明的变量会自动被添加到最近的环境中。在函数内部,最近的环境就是函数的局部环境(也是就是说函数中声明的变量被添加到函数的环境对象中了,同时,也说明外层的执行环境,无法访问内层执行环境中声明的变量)。

如果局部环境中存在着同名标识符,就不会使用父环境中的标识符。

在某个环境中为了读取或写入而饮用一个标识符(变量名)时,必须通过搜索来确定该标识符实际代表什么,搜索过程从作用域链的前端开始,向上(包含环境的角度是向上,作用域链的角度是向下)逐级查询与给定名字匹配的标识符。如果在局部环境中找到了该标识符,搜索过程停止,变量就绪。反之则沿着作用域链接向上层执行环境寻找。搜索过程一直追溯到全局环境的变量对象。如果全局环境的变量对象也没有找到这个标识符,就意味着这个变量未声明。

var color = "blue";
function getColor() {
    return color; // 局部执行环境中没有标识符为 color 的变量,向上层查找
}
console.log(getColor()); // 输出 blue

变量提升

对于JS来说 var a = 2; 分为两步:

  1. 声明:var a;
  2. 赋值:a = 2;

JS 不是严格的自上而下执行代码的语言! 它会将当前作用域的所有变量的声明提升到程序的顶部。

a = 2;
var a;

console.log(a); // 输出 2,而不是 undefined

等同于

var a; // 声明
a = 2; // 赋值

console.log(a);

JS会将变量的声明提升到顶部,可是赋值语句并不会提升。

console.log(a); // 输出 undefeated, 赋值语句不会触发变量提升

a = 2;

为什么会产生变量提升?JS 和其他语言一样,都要经历编译和执行阶段。而 JS 在编译阶段的时候,会搜集所有的变量声明并且提前声明变量,而其他的语句都不会改变他们的顺序,因此,在编译阶段的时候,第一步就已经执行了,而第二步则是在执行阶段执行到该语句的时候才执行。