ECMAScript变量是松散类型的,意思是变量可以用于保存任何类型的数据。有3个关键字可以声明变量:var
、const
和let
。其中,var
在ECMAScript的所有版本中都可以使用,而const
和let
只能在ECMAScript 6及更晚的版本中使用。
var声明
var声明作用域
关键的问题在于,使用 var 操作符定义的变量会成为包含它的函数的局部变量。比如,使用 var 在一个函数内部定义一个变量,就意味着该变量将在函数退出时被销毁:
function test() {
var message = "hi"; // 局部变量
}
test();
console.log(message); // 出错! message在函数调用之后随即会销毁
复制代码
不过,在函数内定义变量时省 略 var 操作符,可以创建一个全局变量:
function test() {
message = "hi"; // 全局变量 }
test();
console.log(message); // "hi" 去掉之前的 var 操作符之后,message 就变成了全局变量。只要调用一次函数 test(),就会定义 这个变量,并且可以在函数外部访问到。
复制代码
var声明提升
所谓的“提升”(hoist),也就是把所有变量声明都拉到函数作用域的顶部。如下面的代码不会报错:
function foo() {
console.log(age);
var age = 26;
}
foo(); // undefined
复制代码
之所以不会报错,是因为 ECMAScript 运行时把它看成等价于如下代码:
function foo() {
var age;
console.log(age);
age = 26;
}
foo(); // undefined
复制代码
此外,反复多次使用 var 声明同一个变量也没有问题:
function foo() {
var age = 16;
var age = 26;
var age = 36;
console.log(age);
}
foo(); // 36
复制代码
let声明
let 跟 var 的作用差不多,但有着非常重要的区别。
区别:
- let 声明的范围是块作用域,而 var 声明的范围是函数作用域。 块作用域 是函数作用域的子集,因此适用于 var 的作用域限制同样也适用于 let。
if (true) {
var name = 'Matt';
console.log(name); // Matt
}
console.log(name) // 'Matt'
if (true) {
let age = 26;
console.log(age); // 26
}
console.log(age); // ReferenceError: age
复制代码
- let不允许同一个块作用域中出现重复声明,会导致报错。
var name;
var name;
let age;
let age; //SyntaxError;标识符age已经声明过了
复制代码
- let声明的变量不会在作用域中被提升。
// name 会被提升
console.log(name); // undefined
var name = 'Matt';
// age 不会被提升
console.log(age); // ReferenceError:age 没有定义
let age = 26;
复制代码
PS: 在 let 声明之前的执行瞬间被称为“暂时性死区”(temporal dead zone),在此阶段引用任何后面才声明的变量都会抛出 ReferenceError。
- 与 var 关键字不同,使用 let 在全局作用域中声明的变量不会成为 window 对象的属性(var 声 明的变量则会)。
var name = 'Matt';
console.log(window.name); // 'Matt'
let age = 26;
console.log(window.age); // undefined
复制代码
const声明
const 的行为与 let基本相同,唯一一个重要的区别是用它声明变量时必须同时初始化变量,且尝试修改const声明的变量会导致运行时错误。
const age = 26;
age = 36; // TypeError: 给常量赋值
复制代码
const所说的一旦声明值就不能改变,实际上指的是:变量指向的那个内存地址所保存的数据不得改动
-
简单类型(number、string、boolean):内存地址就是值,即常量(一变就报错)
-
复杂类型(对象、数组等):地址保存的是一个指针,const只能保证指针是固定的(总是指向同一个地址),它内部的值是可以改变的(不要以为const就安全了!)
所以只要不重新赋值整个数组/对象,因为保存的是一个指针,所以对数组使用的push、shift、splice等方法也是允许的。
const person = {};
person.name = 'Matt'; // ok
复制代码
最后,总结一下它们的异同:
var和let/const的区别:
- 块级作用域
- 不存在变量提升
- 不可重复声明
- let、const声明的全局变量不会挂在window对象下面
const命令两个注意点:
- let可以先声明稍后再赋值,而const在声明之后必须马上赋值,否则会报错
- const 简单类型一旦声明就不能再更改,复杂类型(数组、对象等)指针指向的地址不能更改,内部数据可以更改。
声明风格及最佳实践
- 不使用 var
用let代替var
- const 优先,let 次之
优先使用 const 来声明变量,只在提前知道未来会有修改时,再使用 let。这样可以让开发者更有信心地推断某些变量的值永远不会变,同时也能迅速发现因意外赋值导致的非预期行为。