跳到主要内容

作用域与作用域链

本文讨论 JavaScript 中的作用域与作用域链的概念,以及与执行上下文的对比

省流:

「作用域」在 JavaScript 解析器进行词法语法分析时就确定了,是静态

作用域反映了一个变量或函数可以被访问的范围,主要分三种:全局、函数和 ES6 新增的块级

作用域链,就是当前作用域与上层作用域的变量集合

作用域

作用域就是在运行时代码中的某些特定部分中的变量、函数和对象的可访问性

换句话说,作用域指一个变量或函数可以被访问范围

作用域就是一个独立的地盘,让变量不会外泄、暴露出去,也就是说作用域最大的用处就是隔离变量

  • 作用域控制了变量和函数的可见性和生命周期,这对于避免变量命名冲突和意外的全局变量创建是非常重要的;

  • 作用域链确保了代码在查找变量时有序地从当前作用域向外层作用域逐级查询。

ES5 中只存在两种作用域:全局作用域和函数作用域,ES6 新增了块级作用域

全局作用域

在代码任何地方都能访问到的对象,拥有全局作用域,例如

  • 所有未定义直接赋值的变量

  • 最外层函数和在最外层函数外面定义的变量

  • window 对象的属性

函数作用域

在函数内部定义的变量或函数只能在该函数内部被访问。这些变量在函数外部是不可见的,它们相对于函数外的代码而言是私有的。

值得注意的是,块语句例如条件或者循环语句,不像函数,它们不会创建一个新的作用域

为了减少变量的全局污染避免变量提升(Hoisting)引起的混淆更精确的控制变量生命周期,ES6 引入了块级作用域

块级作用域

由一对大括号{}包围的区域定义了一个块级作用域。使用 letconst 声明的变量只能在这对大括号内被访问。

块级作用域在如下情况会被创建

  • 在一个函数内部
  • 在一个代码块(条件、循环语句)内部

块级作用域有如下特点

  • 无变量提升
  • 禁止重复声明

作用域链

当访问一个变量时,编译器在执行这段代码时,会首先从当前的作用域中是否存在这个标识符,如果没有就一直向上查找,直到全局作用域

作用域链,就是当前作用域与上层作用域的变量集合

作用域链中的值是在函数被创建的时候就已经被确定了,是静态

作用域与执行上下文

JS 属于解释型语言,JS 的执行分为「解释」和「执行」两个阶段

  • 解释阶段:词法分析,语法分析,作用域规则确定

  • 执行阶段:创建执行上下文,执行代码,垃圾回收

JS 在解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定

执行上下文运行时确定,随时可能改变作用域定义时就确定,并且不会改变

面试

function Foo() {
// 改动了全局的 getName 变量
getName = function () {
console.log(1);
};
return this;
}

// Foo 的静态方法
Foo.getName = function () {
console.log(2);
}

// Foo 原型方法
Foo.prototype.getName = function () {
console.log(3);
}

// var getName 变量
var getName = function () {
console.log(4);
}

// getName 函数
function getName() {
console.log(5)
}

// 调用静态方法 2
Foo.getName();

// 全局函数 getName 4
getName();

// 调用 Foo 返回 this,是全局的 this 1
Foo().getName()

// 调用 Foo 时全局变量 getName 被改了 1
getName();

// 实例化 Foo.getName,相当于调用静态方法 2
new Foo.getName();

// 实例化 Foo,调用实例的 getName,没有就去原型上找 3
new Foo().getName()

// 对 Foo 实例运行 new,结果一样 3
new new Foo().getName()