文章目录
var、let 和 const 的区别
关键字 | 变量提升 | 块级作用域 | 重复声明同名变量 | 重新赋值 |
---|---|---|---|---|
var | 有 | 不是 | 允许 | 允许 |
let | 无 | 是 | 不允许 | 允许 |
const | 无 | 是 | 不允许 | 不允许 |
- var声明的变量范围是函数作用域,let和const声明变量的范围是块级作用域
- var声明的变量由变量提升,会将声明部分提升到函数作用域的顶部,let和const不具有变量提升,且具有暂时性死区特征
- var允许在同一作用域中重复声明同一个变量,let和const不允许
- 在全局作用域中,var声明的变量会自动成为window对象的属性,let和const不会
- const和let两者行为基本相同,区别是const 声明的变量必须进行初始化且不可被修改
1. 作用域
1.1 函数作用域和块级作用域
函数作用域
指在程序中定义的函数内部,声明的变量只在该函数内部起作用,并紫萼在函数外部不可访问。也就是说,函数内部的变量具有局部作用域,只能在函数内部使用。
块级作用域
指在一对花括号{}中声明的变量,只能在改代码块内部起作用,并且在代码块外部不可访问。也就是说,花括号内部的变量具有局部作用域,只能在该代码块内部使用。
ES6引入了let和const关键字,JavaScript也有了块级作用域。使用 let 和 const 声明的变量拥有块级作用域,仅在包含它们的最近的一对花括号内有效。这样一来,就可以避免变量污染和冲突,在需要限制变量的作用范围时非常有用。
1.2 var 、let 和 const的声明作用域
function fn() {
if (true) {
var a = 10; //函数作用域,只能在fn()函数中使用
let b = 100; // 块级作用域,只能在if代码块中使用
console.log(a) //可以正常调用
console.log(b)//可以正常调用
}
console.log(a)//a是var声明的 函数作用域 在fn函数内可以正常调用
console.log(b)//b是let声明的 块级作用域 脱离if的大括号无法调用 会报错 b is not defined
}
console.log(a) // a是var声明的 函数作用域 在函数外会报错 a is not defined
console.log(b)//b是let声明的 块级作用域 脱离if的大括号无法调用 会报错 b is not defined
fn()
1.3重复声明同名变量
var允许重新声明 let和const不允许重新声明
if(true) {
let a;
let a;
//error:Identifier 'a' has already been declared 重复声明直接报错
}
if(true) {
//const 声明的变量必须初始化,且不能重复声明
const a = 10;
const a = 20;
//error:Identifier 'a' has already been declared 重复声明直接报错
}
var a = 10;
var a = 20;
//var声明的变量重复声明不会报错,且如果初始化后,后面的会覆盖前面的
var 和 let 同时声明同一个变量也会报错,因为它们声明的并不是不同类型的变量,只是指出变量在相关作用域如何存在,所以对声明冗余报错不会因混用而受影响。
var a;
let a;
//error:Identifier 'a' has already been declared 重复声明直接报错
2. 变量提升
var声明的变量会有变量提升特性
console.log(num1); //输出的结果undefined 原因就是因为有变量提升
var num1 = 10;
//上面的写法相当于下面
var num1;
console.log(num1); //undefined
num1 = 10;
console.log(num2); //Uncaught ReferenceError: num4 is not defined
let num2 = 10;
console.log(num3);//Uncaught ReferenceError: num4 is not defined
const num3 = 10;
3. 暂时性死区
只要作用域内存在 let、const,它们所声明的变量或常量就自动 “绑定” 这个区域,不再受到外部作用域的影响。
let a = 2;
function func() {
console.log(a); // 报错
let a = 1;
}
func();
let a = 2;
function func() {
console.log(a); // 2
}
func();
即:只要作用域内出现了同名的 let 或 const,那么就会去找这个量(向前找),如果找不到也不会跳去外部找,只会直接报错!
只要我们遵守 “先声明后使用”,那么其实就基本不会遇到变量提升及暂时性死区问题。
4. 重新赋值
//let 声明的变量可以重新赋值
let num1 = 10;
num1 = 20;
console.log(num1); // 20
//const 只能在声明时赋值,之后不能再重新赋值
const num2 = 10;
num2 = 20; // Uncaught TypeError: Assignment to constant variable.
5. 全局声明
顶层对象,在浏览器环境指的是window
对象,在 Node 指的是global
对象。ES5 之中,顶层对象的属性与全局变量是等价的。
window.a = 1;
a // 1
a = 2;
window.a // 2
上面代码中,顶层对象的属性赋值与全局变量的赋值,是同一件事。
顶层对象的属性与全局变量挂钩,被认为是 JavaScript 语言最大的设计败笔之一。这样的设计带来了几个很大的问题,首先是没法在编译时就报出变量未声明的错误,只有运行时才能知道(因为全局变量可能是顶层对象的属性创造的,而属性的创造是动态的);其次,程序员很容易不知不觉地就创建了全局变量(比如打字出错);最后,顶层对象的属性是到处可以读写的,这非常不利于模块化编程。另一方面,window
对象有实体含义,指的是浏览器的窗口对象,顶层对象是一个有实体含义的对象,也是不合适的。
ES6 为了改变这一点,一方面规定,为了保持兼容性,var
命令和function
命令声明的全局变量,依旧是顶层对象的属性;另一方面规定,let
命令、const
命令、class
命令声明的全局变量,不属于顶层对象的属性。也就是说,从 ES6 开始,全局变量将逐步与顶层对象的属性脱钩。
var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
console.log(window.a); // 1
let b = 1;
console.log(window.b); // undefined
上面代码中,全局变量a
由var
命令声明,所以它是顶层对象的属性;全局变量b
由let
命令声明,所以它不是顶层对象的属性,返回undefined
。
6. for循环中的var 和 let 声明的区别
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 输出的是我们期望的值,也就是循环执行过程中每个迭代变量的值。
7. 执行速度和内存占用
JavaScript中,代码执行到变量作用域之外后,会销毁变量并释放其占用的内存。由于let和const的作用域相较于var更小,因此它们通常有更快的执行速度和较小的占用内存空间。