var 声明的范围是函数作用域,let 和 const 声明的范围是块作用域
var 声明的变量会被提升到函数作用域的顶部,let 和 const 声明的变量不存在提升,且具有暂时性死区特征
var 允许在同一个作用域中重复声明同一个变量,let 和 const 不允许
在全局作用域中使用 var 声明的变量会成为 window 对象的属性,let 和 const 声明的变量则不会
const 的行为与 let 基本相同,唯一 一个重要的区别是,使用 const 声明的变量必须进行初始化,且不能被修改
关键字 | 变量提升 | 块级作用域 | 重复声明同名变量 | 重新赋值 |
---|---|---|---|---|
var | √ | × | √ | √ |
let | × | √ | × | √ |
const | × | √ | × | × |
var
没有块级作用域,而let
声明的范围是块作用域; 一对大括号 就是 一个块级作用域
if (true) {
var message = "hello";
console.log(message); // hello
}
console.log(message); // hello
if (true) {
let message = "hello";
console.log(message); // hello
}
console.log(message); // error: message is not defined
let
不允许同一个块作用域中出现冗余声明:
if (true) {
// error: 无法重新声明块范围变量“a”
let a;
let a;
}
JS 引擎会记录用于变量声明的标识符及其所在的块作用域,因此嵌套使用相同的标识符不会报错,这是因为同一个块中没有重复声明:
let a = 666;
console.log(a); // 666
if (true) {
let a = '啊哈哈';
console.log(a); // 啊哈哈
}
var
和let
声明的并不是不同类型的变量,它们只是指出变量在相关作用域如何存在,所以对声明冗余报错不会因混用var
和let
而受影响:
// error
var a;
let a;
// error
let b;
var b;
//用var命名的变量有变量提升
console.log(num1); // undefined
var num1 = 10;
// 以上代码运行时,相当于下面的写法
var num2; // 声明提升到作用域最顶端
console.log(num2); // undefined
num = 10;
/*****************************************/
//用 let 或 const 命名的变量有变量提升
console.log(num3); // Uncaught ReferenceError: num3 is not defined
let num3 = 10;
console.log(num4); // Uncaught ReferenceError: num4 is not defined
const num4 = 10;
只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量
var tmp = 123; // 声明
if (true) {
tmp = 'abc'; // 报错 因为本区域有tmp声明变量
let tmp; // 绑定if这个块级的作用域 不能出现tmp变量
}
暂时性死区和不能变量提升的意义在于: 为了减少运行时错误,防止在变量声明前就使用这个变量,从而导致意料之外的行为。
//var 关键字可以声明同名变量,实际第二次声明是对第一次声明的变量重新赋值
var num1 = 10;
var num1 = 20;
console.log(num1); // 20
//let 和const 关键字不能重复声明同名变量,即使之前是用var声明的也会报错
var num2 = 10;
let num2 = 20; // Uncaught SyntaxError: Identifier 'num2' has already been declared
//let 和 var 在声明变量时,可以不用初始化
let num3;
console.log(num3); // undefined
var num4;
console.log(num4); // undefined
//const 声明常量时必须初始化,因为 `const` 关键字声明的是常量,声明后不能再赋值
const num5; // Uncaught SyntaxError: Missing initializer in const declaration
//let 声明的变量可以重新赋值
let num1 = 10;
num1 = 20;
console.log(num1); // 20
//const 只能在声明时赋值,之后不能再重新赋值
const num2 = 10;
num2 = 20; // Uncaught TypeError: Assignment to constant variable.
使用var
在全局作用域中声明的变量会成为window
对象的属性,let
和const
声明的变量则不会:
var a = 666;
console.log(window.a); // 666
let b = 666;
console.log(window.b); // undefined
const c = 666;
console.log(window.c); // undefined
for (var i = 0; i < 5; i++) {
setTimeout( () => {
console.log(i); // 5、5、5、5、5
}, 0 )
}
for (let i = 0; i < 5; i++) {
setTimeout( () => {
console.log(i); // 0、1、2、3、4
}, 0 )
}
var是因为在退出循环时,迭代变量保存的是导致循环退出的值,也就是 5。在之后异步执行超时逻辑时,所有的i
都是同一个变量,因此输出的都是同一个最终值。
而在使用let
声明迭代变量时,JS 引擎在后台会为每个迭代循环声明一个新的迭代变量,每个 setTimeout 引用的都是不同的变量实例,所以 console.log 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。