JavaScript var、let、const 关键字

var 关键字

函数作用域

使用 var 操作符定义的变量会成为包含它的函数的局部变量。比如,使用 var 在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:

function sayHello() {
	var message = 'Hello'
	console.log(message)
}

sayHello()	// Hello
console.log(message)	// ReferenceError: message is not defined

全局变量

去掉之前的 var 操作符之后,message 就变成了全局变量。只需要调用一次函数 sayHello(),就会定义这个全局变量,并且可以在函数外部访问到:

function sayHello() {
	message = 'Hello'
	console.log(message)
}

sayHello()	// Hello
console.log(message)	// Hello

注意:

  • 声明全局变量时,必须给全局变量赋值,否则会报错;
  • 虽然可以通过省略 var 操作符定义全局变量,但不推荐这么做。在局部作用域中定义的全局变量很难维护,也会造成困惑。
  • 严格模式下,如果像这样给未声明的变量赋值,则会导致抛出 ReferenceError

变量提升

使用 var 时,下面的代码不会报错。这是因为使用这个关键字声明的变量会自动提升到函数作用域顶部:

function sayHello() {
	console.log(message)
	var message = 'Hello'
}

sayHello()	// undefined

之所以不会报错,是因为 ECMAScript 运行时把它看成等价于如下代码:

function sayHello() {
	var message
	console.log(message)
	message = 'Hello'
}

sayHello()	// undefined

这就是所谓的“提升”(hoist),也就是把所有变量声明都拉到函数作用域的顶部。此外,反复多次使用 var 声明同一个变量也没有问题:

function sayHello() {
	var message = 'Hello'
	var message = 'World'
	var message = 'Hello, World!'
	console.log(message)
}

sayHello()	// Hello, World!

let 关键字

块级作用域

块级作用域的表现形式和 Java、Python 等语言变量一致。块作用域是函数作用域的子集,因此适用于 var 的作用域限制同样也适用于 let

for (var i = 0; i < 5; i++) {
	// nothing to do here.
}
console.log(i)	// 5

for (let j = 0; j < 5; j++) {
	// nothing to do here.
}
console.log(j)	// ReferenceError: j is not defined

在上述代码中,因为用 var 声明的变量具有函数作用域的特性。对于 for 循环,因为不是函数,所以在语句内声明的变量在外部也能使用;而用 let 声明的变量具有块级作用域的特性,所以在语句块({})内声明的变量,只能在语句块内使用,退出语句块时变量会被销毁。

暂时性死区

letvar 的另一个重要区别就是:let 声明的变量不会在作用域中被提升。

console.log(name)	// undefined
var name = '张三'

console.log(age)	// ReferenceError: Cannot access 'age' before initialization
let age = 22

在解析代码时,JavaScript 引擎也会注意出现在块后面的 let 声明,只不过在此之前不能以任何方
式来引用未声明的变量。在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此
阶段引用任何后面才声明的变量都会抛出 ReferenceError

let name = '张三'
let name = '李四'	// SyntaxError: Identifier 'name' has already been declared
let name = '王五'
console.log(name)

因为使用 let 声明的变量不存在变量提升现象,所以不能使用 let 同时声明多个相同标识符的变量。但是因为使用 let 声明的变量具有块级作用域的特性,在不同块级作用域中声明相同的变量是合法的:

let name = '张三'

if (true) {
	// console.log(name)	// ReferenceError: Cannot access 'name' before initialization
	let name = '李四'
	console.log(name)	// 李四
}

console.log(name)	// 张三

全局声明

var 关键字不同,使用 let 在全局作用域中声明的变量不会成为 window 对象的属性(var
明的变量则会)。

var name = '张三'
console.log(window.name)	// 张三

let age = 22
console.log(window.age)		// undefined

for 循环比较

for (var i = 0; i < 5; i++) {
	setTimeout(() => console.log(i), 500)
}

// 5 5 5 5 5

for (let i = 0; i < 5; i++) {
	setTimeout(() => console.log(i), 500)
}

// 0 1 2 3 4

第一个输出结果可能有点超乎我们的意料,仔细想想,这是因为 var 声明变量函数作用域特性导致的,最后打印的都是同一个变量 i,其值为 5;而在使用let 声明迭代变量时,JavaScript 引擎在后台会为每个迭代循环声明一个新的迭代变量。每个setTimeout 引用的都是不同的变量实例,所以console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。

这种每次迭代声明一个独立变量实例的行为适用于所有风格的 for 循环,包括 for-infor-of 循环。

const 关键字

const 的行为与let 基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改 const 声明的变量会导致运行时错误。

JavaScript 引擎会为for 循环中的let 声明分别创建独立的变量实例,虽然const 变量跟let 变量很相似,但是不能用const 来声明迭代变量(因为迭代变量会自增)。

for (const i = 0; i < 5; i++) {	// TypeError: Assignment to constant variable.
	// nothing to do here.
}

不过,如果你只想用const 声明一个不会被修改的for 循环变量,那也是可以的。也就是说,每次迭代只是创建一个新变量。这对for-offor-in 循环特别有意义:

const stu = {
	name: '张三',
	age: 22,
	gender: '男'
}

for (const keyVal of Object.entries(stu)) {
	console.log(keyVal)
}

// [ 'name', '张三' ]
// [ 'age', 22 ]
// [ 'gender', '男' ]

总结

不使用 var

有了letconst,大多数开发者会发现自己不再需要var 了。限制自己只使用letconst有助于提升代码质量,因为变量有了明确的作用域、声明位置,以及不变的值。

const 优先,let 次之

使用const 声明可以让浏览器运行时强制保持变量不变,也可以让静态代码分析工具提前发现不合法的赋值操作。因此,很多开发者认为应该优先使用const 来声明变量,只在提前知道未来会有修改时,再使用let。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为。

原文地址:https://www.cnblogs.com/haveadate/p/var_let_const.html