Skip to content

作用域

作用域指的是程序中定义变量的区域,它决定了当前执行代码对变量的访问权限。

在 JavaScript 中,作用域分为三种:

  • 函数作用域
  • 全局作用域
  • 块级作用域

函数作用域

每个函数都会创建自己的作用域,在函数内部声明的变量,只能在函数内部访问,函数外部无法访问。

js
// 此处无法使用bar变量
function foo () {
  var bar = 'bar';
  // 此处可以使用bar变量
}

由于只能在函数内部识别局部变量,因此能够在不同函数中使用同名变量。

在函数开始时会创建局部变量,在函数完成时会删除它们。

全局作用域

函数之外声明的变量,会成为全局变量,即网页的所有脚本和函数都能够访问它。

js
var bar = 'bar';
// 此处可以使用bar变量
function foo () 
  // 此处也可以使用bar变量
}

块级作用域

通过关键字letconst即可创建块级作用域,所声明的变量在指定块的作用域外无法被访问。

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

  • 在一个函数内部

  • 在一个代码块内部,即{}

js
if (true) {
  let bar = 'bar';
  // 此处可以使用bar变量
} else {
  // 此处无法使用bar变量
}
// 此处无法使用bar变量

块级作用域有以下几种特点:

  • 不会变量提升
  • 不能重复声明

作用域链

当我们在函数中访问一个变量时,首先会在当前的函数作用域中查找,如果没找到,则会到父级作用域中查找,以此类推向上查找形成的链条就是作用域链。

js
function foo (a) {
  var b = a * 2;
  function bar (c) {
    console.log(a, b, c);
  }
  bar(b * 3);
}
foo(2); // 2 4 12

上述代码中,我们可以看到在 bar 函数的函数作用域中只有形参 c,a 和 b 变量都是从外部函数 foo 的函数作用域中获取的。

因此这段代码形成的作用域链如下:

  1. 全局作用域
  2. foo 函数作用域
  3. bar 函数作用域

词法作用域

JavaScript 的作用域就是词法作用域。

词法作用域指的是,定义表达式并能被访问的区间。简单来说,一个声明(变量、函数)的词法作用域就是它被声明时所在的作用域。

词法作用域又叫静态作用域,与之对应的就是动态作用域,那么它们的区别是什么呢?

这里通过一个代码示例来说明:

js
var value = 1;

function foo() {
  console.log(value);
}

function bar() {
  var value = 2;
  foo();
}

bar();

// 代码运行结果是?

上述代码中,一共有三个作用域:

  • 全局作用域
  • foo 函数作用域
  • bar 函数作用域

其中 foo 函数中访问了本地作用域中不存在的变量 value,那么根据作用域链,应该要去 foo 函数的父级作用域中查找。

问题来了,foo 函数的父级作用域是什么呢?是它调用时所在的 bar 函数作用域?还是它定义时所在的全局作用域?

问题的关键就在于词法作用域,在我们介绍词法作用域的时候已经说过,一个声明(变量、函数)的词法作用域就是它被声明时所在的作用域。

因此,foo 函数的词法作用域就是在声明 foo 函数所在的位置,即全局作用域。那么 foo 函数的父级作用域就是全局作用域。代码的运行结果是1。

如果是动态作用域,上面的代码运行结果应该是2。但是动态作用域只有少数的语言(如 bash 脚本)使用。

作用域和执行上下文的区别

作用域是我们能够访问的变量对象,执行上下文是执行环境。

作用域是在定义时确定的,执行上下文是在调用时确定的。

总结

作用域指的是我们有权访问的变量对象(Variable Object)。

JavaScript 中,作用域分为三种:

  • 函数作用域
  • 全局作用域
  • 块级作用域

当我们在函数中访问一个变量时,首先会在当前的函数作用域中查找,如果没找到,则会到声明该函数的作用域中查找,以此类推向上查找形成的链条就是作用域链。