- 你不知道的JavaScript(下卷)
- (美)凯尔·辛普森
- 1194字
- 2020-08-29 01:58:06
2.2 变量
在JavaScript中,变量的名称(包括函数名称)必须是有效的标识符。考虑到Unicode这样的非传统字符的情况,标识符中有效字符的严格完整规则有点复杂。如果只是考虑常用的ASCII字母数字的话,那么规则是非常简单的。
标识符必须由a~z、A~Z、$或 开始。它可以包含前面所有这些字符以及数字0~9。
一般来说,变量标识符的这个规则对属性命名也同样适用。但是,有些单词不能用作变量名,但可以作为属性名。这些单词被称为“保留词”,其中包括JavaScript关键字(for、in、if等)以及null、true和false。
有关保留字的更多信息,参见本系列《你不知道的JavaScript(中卷)》第一部分的附录A。
函数作用域
如果使用关键字var声明一个变量,那么这个变量就属于当前的函数作用域,如果声明是发生在任何函数外的顶层声明,那么这个变量则属于全局作用域。
1. 提升
无论var出现在一个作用域中的哪个位置,这个声明都属于整个作用域,在其中到处都是可以访问的。
这一行为被比喻地称为提升(hoisting), var声明概念上“移动”到了其所在作用域的最前面。从技术上来说,可以通过如何编译代码更精确地解释这个过程,我们暂时先不赘述这些细节。
考虑:
var a = 2;
foo(); // 因为foo()而运行 // 声明是“被提升的” function foo() { a = 3; console.log( a ); // 3 var a; // 声明是“被提升的” // 到foo()的顶端 } console.log( a ); // 2
在变量声明出现之前,依靠变量提升在其作用域使用这个变量并不常见,也并不是一个好的想法;这样的代码可能会令人非常迷惑。相比之下,使用提升后的函数声明则要常见得多,就像我们在foo()的正式声明出现前就调用了它。
2. 嵌套作用域
声明后的变量在这个作用域内是随处可以访问的,包括所有低层/内层的作用域。举例来说:
function foo() { var a = 1; function bar() { var b = 2; function baz() { var c = 3; console.log( a, b, c ); // 1 2 3 } baz(); console.log( a, b ); // 1 2 } bar(); console.log( a ); // 1 } foo();
注意,在上述示例中,c在bar()的内部是不可访问的,因为它只声明在内层baz()作用域,b在foo()中是不可访问的,也是同样的原因。
如果试图在一个作用域中访问一个不可访问的变量,那么就会抛出ReferenceError。如果试图设定尚未声明的变量,那么就会导致在顶层全局作用域创建这个变量(不好!)或者出现错误,这要根据是否处于“严格模式”而定(参见2.4节)。我们来看一下:
function foo() { a = 1; // a没有正式声明 } foo(); a; // 1--哎呀,自动全局变量 :(
这是一个很差的实践。不要这么做!一定要正式声明你的变量。
除了在函数层级声明变量,ES6还支持通过let关键字声明属于单独块({ .. }对)的变量。除了细节上有一些微小差别,这里的作用域规则和我们在函数中看到的基本上是相同的:
function foo() { var a = 1; if (a >= 1) { let b = 2; while (b < 5) { let c = b * 2; b++; console.log( a + c ); } } } foo(); // 5 7 9
因为使用了let而不是var,所以b只属于if语句,不属于整个foo()函数的作用域。与此类似,c只属于while循环。块作用域非常有助于更细化地管理变量作用域,从而更容易随着时间的发展而维护代码。
有关作用域的更多信息,参见本系列中的《你不知道的JavaScript(上卷)》第一部分。有关let块作用域的更多信息,参见本书第二部分。