JavaScript语言基础之变量
本文最后更新于 2025年7月30日 下午
ECMAScript 变量是松散类型的,意思是变量可以用于保存任何类型的数据。每个变量只不过是一个用于保存任意值的命名占位符。
有 3 个关键字可以声明变量:var
、const
、let
。其中,var
在 ECMAScript 的所有版本中都可以使用,而 const
和 let
只能在 ECMAScript 6 及更晚的版本中使用。
var
要定义变量,可以使用 var
后跟变量名,可以用它保存任何类型的值。(不初始化的情况下,变量会保存特殊值为 undefined
)
1 |
|
ECMAScript 实现变量初始化,因此可以同时定义变量并设置它的值。初始化变量并不会将它标识为某一类型,只是简单的赋值。随后,不仅可以改变保存的值,也可以改变值的类型。虽然不推荐改变变量保存值的类型,但这在 ECMAScript 中是完全有效的。
1 |
|
声明作用域
使用 var
定义的变量会成为包含它的函数的局部变量。比如,使用 var
在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁。
不过在函数内定义变量时省略 var
可以创建一个全局变量。只要调用一次函数,就会定义这个变量,而且可以在函数外部访问到。虽然可以通过省略 var
定义全局变量,但不推荐这么做。在局部作用域中定义的全局变量很难维护,也会造成困惑。这是因为不能一下子断定省略 var
是不是有意而为之。在严格模式下,如果像这样给未声明的变量赋值,则会导致抛出 ReferenceError。
1 |
|
如果需要定义多个变量,可以在一条语句中用逗号分隔每个变量(及可选的初始化)。
ECMAScript 是松散类型的,所以使用不同数据类型初始化的变量可以用一条语句来声明。插入换行和空格缩进并不是必需的,但这样有利于阅读理解。
1 |
|
在严格模式下,不能定义名为 eval 和 arguments 的变量,否则会导致语法错误。
声明提升
使用 var
声明的变量会自动被提升到作用域顶部,在声明前先读取变量不会报错;省略 var
声明会定义全局变量,但不会提升到作用域顶部。
1 |
|
重复声明
此外,反复多次使用 var
声明同一个变量也没有问题。
1 |
|
全局声明
var
在全局作用域中声明变量,该变量会成为 window
对象的属性。
let
let
跟 var
的作用差不多,但有着非常重要的区别。
声明作用域
最明显的区别是,let
声明的范围是块作用域,var
声明的范围是函数作用域,块作用域是函数作用域的子集,因此适用于 var
的作用域限制同样也适用于 let
。
1 |
|
声明提升
let
与 var
的另一个重要的区别,就是 let
声明的变量不会在作用域中被提升。
在解析代码时,JavaScript 引擎也会注意出现在块后面的 let
声明,只不过在此之前不能以任何方式来引用未声明的变量。在 let
声明之前的执行瞬间被称为“暂时性死区” ,此阶段引用任何后面才声明的变量都会抛出 ReferenceError。
1 |
|
重复声明
let
不允许同一个块作用域中声明已被声明过的变量,这样会报错。而且,被 let
声明过的变量也不能被再次声明。
1 |
|
全局声明
与 var
不同,使用 let
在全局作用域中声明的变量不会成为 window
对象的属性。
条件声明
在使用 var
声明变量时,由于声明会被提升,JavaScript 引擎会自动将多余的声明在作用域顶部合并为一个声明。因为 let
的作用域是块,所以不可能检查前面是否已经使用 let
声明过同名变量,同时也就不可能在没有声明的情况下声明它。
使用 try/catch
语句或 typeof
操作符也不能解决,因为条件块 let
声明的作用域仅限于该块。
for
循环中
在 let
出现之前,for
循环定义的迭代变量会渗透到循环体外部。改成使用 let
之后,这个问题就消失了,因为迭代变量的作用域仅限于 for
循环块内部。
1 |
|
在使用 var
的时候,最常见的问题就是对迭代变量的奇特声明和修改。在退出循环时迭代变量保存的是导致循环退出的值。在之后执行超时逻辑时,所有的变量都是同一个变量,因而输出的都是同一个最终值。而在使用 let
声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。每个超时逻辑引用的都是不同的变量实例。
这种每次迭代声明一个独立变量实例的行为适用于所有 for
循环,包括 for-in
和 for-of
循环。
1 |
|
const
const
的行为与 let
基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。
const
声明的限制只适用于它指向的变量的引用。换句话说,如果 const
变量引用的是一个对象,那么修改这个对象内部的属性并不违反 const
的限制。
JavaScript 引擎会为 for
循环中的 let
声明分别创建独立的变量实例,虽然 const
变量跟 let
变量很相似,但是不能用 const
来声明迭代变量(因为迭代变量会自增)。
如果只想用 const
声明一个不会被修改的 for
循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。这对 for-of
和 for-in
循环特别有意义。
最佳实践
ECMAScript 6 增加 let
和 const
从客观上为这门语言更精确地声明作用域和语义提供了更好支持。行为怪异的 var
所造成的各种问题,已经让 JavaScript 社区为之苦恼了很多年。随着这两个新关键字的出现,新的有助于提升代码质量的最佳实践也逐渐显现。
不使用
var
限制自己只使用let
和const
有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。const
优先,let
次之
使用const
声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用const
来声明变量,只在提前知道未来会有修改时,再使用let
。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为。