变量声明
ECMAScript 变量是松散类型的,变量可以用于保存任何类型的数据,每个变量只不过是一个用于保存任意值的命名占位符。有三个关键字可以声明变量:var
、let
和const
。其中var
在所有版本中都可以使用,而let
和const
只能在 ECMAScript6 及之后版本中使用。
1. var
关键字
var message
定义一个名为message
的变量,可以用于保存任何类型的值。在不初始化的情况下,变量会保存一个特殊值undefined
。
var message = 'hi'
message = 100 // 合法,但不推荐
在声明的同时可以进行初始化。变量的初始化只是简单的赋值,并不标识变量类型,之后不仅可以改变变量保存的值,还可以改变值的类型。
1.1 var
声明作用域
使用var
操作符在全局作用域中声明的变量将成为window
对象的属性。
var name = 'Stan'
console.log(window.name) // Stan
使用var
操作符定义的变量会成为包含它的函数的局部变量。
function test() {
var message = 'h1' // 局部变量
}
test() // 函数调用之后其中的局部变量即被销毁
console.log(message) // error
在函数内定义变量时省略var
操作符,将创建一个全局变量。(在严格模式下将抛出ReferenceError
)
function test() {
message = 'hi' // 全局变量
}
test()
console.log(message) // hi
可以在一条语句中定义多个变量,并进行初始化(可选的)。
var message = 'hi',
age = 20,
found = false
1.2 var
声明提升
使用var
操作符声明的变量会自动提升到函数作用域的顶部。变量提升(hoist)指的是将变量声明提升到函数作用域的顶部。
function foo() {
console.log(age)
var age = 20
}
foo() // undefined
/* 等价于 */
function foo() {
var age
console.log(age)
age = 20
}
此外,可以多次使用var
声明同一个变量,而let
和const
均不允许在作用域内出现冗余声明。混用三个操作符也要遵从操作符的规则。
var age = 20
var age = 21
var age = 22
console.log(age) // 22
let name
let name // SyntaxError
var name = 'xiaoming'
var name = 'xiaohong' // OK
let name // SyntaxError
let age
var age // SyntaxError
2. let
关键字
let
和var
的作用类似,主要区别在于,let
声明的范围是块作用域,而var
声明的范围是函数作用域。
if (true) {
var name = 'Stan'
console.log(name) // Stan
}
console.log(name) // Stan
if (true) {
let age = 20 // 作用域仅限于当前块
console.log(age) // 20
}
console.log(age) // ReferenceError
JavaScript 引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同标识符不会报错,因为同一个块中没有重复声明。
var name = 'xiaoming'
let age = 20
console.log(name) // xiaoming
console.log(age) // 20
if (true) {
var name = 'xiaohong'
let age = 19
console.log(name) // xiaohong
console.log(age) // 19
}
2.1 暂时性死区
let
和var
的另一个重要区别在于,let
声明的变量不会在作用域内被提升。
console.log(name) // undefined
console.log(age) // ReferenceError
var name = 'xiaoming'
let age = 20
在解析代码时,JavaScript 引擎也会注意到出现在块后部的let
声明,但在此之前不能以任何方式来引用未声明的变量,否则将抛出ReferenceError
。在let
声明前的执行瞬间称为暂时性死区(temporal dead zone)。
2.2 全局声明
与var
关键字不同,使用let
在全局作用域中声明的变量不会成为window
对象的属性。
let age = 10
console.log(window.age) // undefined
2.3 条件声明
JavaScript 不支持条件声明。
使用try/catch
语句或typeof
操作符也无法解决,因为条件块中let
声明的作用域仅限于该块。
if (typeof name === 'undefined') {
let name // name的作用域仅限于此块作用域
}
name = 'Stan' // 无法达到目的,形同全局赋值
try {
console.log(age) // 若未声明age,则会报错
} catch (error) {
let age // age的作用域仅限于此块作用域
}
age = 20 // 无法达到目的,形同全局赋值
2.4 for
循环中的let
声明(包括for-in
和for-of
循环)
for (var i = 0; i < 5; i++) {}
console.log(i) // 5
for (var j = 0; j < 5; j++) {
setTimeout(() => console.log(j))
}
// 5 5 5 5 5
for (let m = 0; m < 5; m++) {}
console.log(m) // ReferenceError
for (let n = 0; n < 5; n++) {
setTimeout(() => console.log(n))
}
// 1 2 3 4 5
3. const
关键字
const
关键字与let
基本相同,唯一重要的区别在于使用const
关键字声明时必须同时初始化变量,且不能修改该变量的值,否则将抛出SyntaxError
。
const
声明的变量只适用于它指向的变量的引用,即若const
变量引用一个对象,修改该对象内部的属性并不违反const
的限制。
4. 声明风格及最佳实践
const
优先,let
次之,不使用var
。