Logo
Section 1 / 7

变量作用域

JavaScript中的作用域指的是可用的函数和变量的集合。了解变量和函数在何处可用,以及JavaScript引擎如何为我们提供所需的值。

检查点

变量作用域

JavaScript中的作用域指的是可用的函数和变量的集合。了解变量和函数在何处可用,以及JavaScript引擎如何为我们提供所需的值。

概述

作用域的概念指的是变量在代码中的可用性。如果一个变量是在另一个函数中创建的,我们能否在该函数中使用它?如果一个函数内部包含另一个函数,它们是否可以访问彼此的变量?这些问题看似简单,但对于不了解其原理的人来说却常常引起困惑。让我们直接进入主题。

JavaScript 中有两种作用域:全局作用域和函数作用域。

全局作用域

当我们开始在一个文件中编写代码时,就已经自动处于该文件的全局作用域中。在文件中声明的变量都会被放入全局作用域。

1var x = 5; 2var y = 'abc'; 3function fn() {};

此时,x、y 和 fn 都处于全局作用域中。这是我们可以访问的最高级作用域。全局作用域中的任何内容都可以在整个文件中被访问。

局部作用域

函数拥有自己的局部作用域。这个局部作用域可以看作是嵌套在全局作用域之内的。

1var x = 5; 2var y = 'abc'; 3 4function fn() { 5 var insideFnScope = true; 6};

内层作用域可以访问外层作用域。这意味着在函数 fn 内部时,我们可以访问变量 x 和 y。如果我们处在某个“作用域椭圆”内部,就可以访问该椭圆外部的所有内容。在函数中,我们可以向外查看并访问父级作用域(即全局作用域)中的变量。

相反的情况则不成立。如果我们处于全局作用域中(即不在函数内部),我们就处于更大的椭圆中。我们无法向下查看 fn 的作用域来访问其中的变量。

我们可以查看当前作用域及其外部的作用域,但不能查看内部作用域。让我们通过一个例子来演示这一概念。

1var x = 5; 2 3function fn() { 4 var insideFnScope = 10; 5 console.log(insideFnScope); 6 console.log(x); 7}; 8 9fn(); 10// -> 10 11// -> 5 12 13console.log(insideFnScope); 14// -> Uncaught ReferenceError: insideFnScope is not defined

在 fn 函数内部,我们可以访问 fn 自身的所有变量以及全局作用域中的变量,因为我们具有向外访问的能力。我们在函数内部正确地输出了 x 和 insideFnScope。

当我们在函数外部时,就无法访问在函数内部声明的变量。尝试这样做会导致引用错误。

更深入的理解

让我们进一步探讨这些概念。

1var x = 5; 2var y = 10; 3 4function fn() { 5 var y = 20; 6 console.log(x); 7 console.log(y); 8} 9 10fn(); 11// -> 5 12// -> 20 13 14console.log(x); // -> 5 15console.log(y); // -> 10

让我们分析一下这里发生了什么。我们首先创建了变量 x 和 y。由于它们不在函数内部,因此属于全局作用域。

在函数内部,我们又创建了一个名为 y 的新变量,位于函数的局部作用域中。这与第一个 y 是不同的变量。

JavaScript 中存在变量查找机制。当我们尝试访问一个变量时,JavaScript 引擎会先检查当前作用域。如果找不到,就会向上一级父作用域查找,一直持续到找到该变量或到达全局作用域为止。如果全局作用域中也没有该变量,就会抛出引用错误。

这种查找只向外部进行。这意味着函数只能访问全局作用域以及它所处的函数的作用域。

在这个例子中,当我们调用 fn 并打印 x 和 y 时,引擎首先在 fn 的局部作用域中查找这两个变量。

查找 x 时,引擎在局部作用域中未找到,于是向上一级外层作用域查找,最终在全局作用域中找到了 x,并使用了它的值。

查找 y 时,引擎在局部作用域中找到了 y,因此直接使用了局部的值,无需继续向上查找。

修改外层作用域

我们已经了解了如何访问局部作用域之外的变量,以及如何在局部作用域中声明新变量。那么,当我们处于内层作用域时,是否可以修改外层作用域中的变量?

1var x = 5; 2 3function fn() { 4 x = 10; 5 y = 20; 6}; 7 8fn(); 9console.log(x); // -> 10 10console.log(y); // -> 20

这里输出了 10……为什么?难道第一个 console.log 不应该输出 5 吗?因为 fn 内部的 x 应该是一个新的局部变量?第二个 console.log 不应该报错吗?

注意,在 fn 函数内部,当我们设置 x = 10 和 y = 20 时,并没有使用 var 关键字。这才是关键所在。

函数可以访问并修改其外层作用域中的变量。当我们省略 var 时,JavaScript 引擎不会在局部作用域中创建新的变量实例。相反,它会先尝试查找我们要修改的变量。如果在局部作用域中找到了,就修改局部作用域中的变量;如果没有找到,就会不断向上查找,直到找到为止。

对于 x 来说,引擎先在局部作用域中查找,未找到,然后向上查找,在全局作用域中找到了 x,于是修改了全局作用域中的值。

对于 y 来说,引擎在任何作用域中都没有找到它。当我们省略 var 进行变量赋值且引擎找不到该变量时,它会自动将该变量创建在全局作用域中。这就是为什么我们在函数外部能正常输出 20,而没有任何问题。

最佳实践

在全局作用域中创建新变量是一种不良做法。这会导致代码混乱,并引发难以排查的 bug。它会污染全局作用域。如果你发现自己在没有使用 var 的情况下需要在全局作用域中创建新变量,建议重构代码。通常有更合适的方式来实现你的目标。

测验时间

让我们通过一些例子来练习。试着自己判断会发生什么。

回顾下面的第3个问题。

在任何函数执行之前,外层作用域中有三个变量。

(作用域图示:变量在 outerFn 执行前的状态)

当 outerFn 开始执行时,它创建了自己的 x 变量,并修改了全局的 y。

(作用域图示:outerFn 已开始执行但 innerFn 尚未执行的状态)

当 innerFn 执行时,它修改了 outerFn 的 x,并创建了自己的 y。全局作用域保持不变。这是 innerFn 返回前的最终作用域状态。

(作用域图示:innerFn 已开始执行后的状态)

作用域的内容就到这里。

Quiz

单选题

以下代码会打印什么?

1 / 3

变量提升