let、const与var的区别
18888628835 opened this issue · 0 comments
let、const与var的区别
let声明和var声明受块级作用域限制不同
如下代码
{
let a=10;
var b=20
}
a //Uncaught ReferenceError: a is not defined
b //20
当被块级作用域包围时,可以看到let声明使得块级作用域外部无法访问a这个变量,但是var声明的b却不会有这样的问题。
说明var不受块级作用域的影响,而let声明只在块级作用域中有效。
这就使得在循环时,let比var更有优势,因为不会出现变量外泄问题。
for(let i=0;i<5;i++){
...
}
console.log(i) //Uncaught ReferenceError: i is not defined
for(var i=0;i<5;i++){
...
}
console.log(i) //5
当for循环配合var声明时,由于不受块级作用域影响,全局只有一个作用域,i变量所在的作用域就是全局作用域,这就使得i变量会外泄。
当for循环配合let声明时,则会创建独立的块级作用域,与全局作用域不同,所以我们在外层无法获取到内部的i变量。
let声明配合循环可以创建独立的作用域
上面说到let声明配合循环会生成独立的作用域,下面有个更好的例子
for(var i=0;i<6;i++){
setTimeout(()=>{
console.log(i)
},0)
}
//6 6 6 6 6 6
这个例子在《你不知道的JavaScript》中有提及,被我写到博客上多次。
由于这个循环只有一个全局作用域,所以当循环结束,异步代码执行时,只能获取到全局作用域上的i,这时候i已经变成了6,所以打出来就是6个6。
例子换成let就不一样了
for(let i=0;i<6;i++){
setTimeout(()=>{
console.log(i)
},0)
}
//0 1 2 3 4 5
这是由于每次循环都会生成一个作用域,当前循环中的i就存在于这个作用域中,所以实际上会生成6个不同的作用域,那么当异步代码执行时,会从对应的作用域读取i,所以结果就是0 1 2 3 4 5
。
再来看一个例子:
var a = [];
for (var i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 10
由于只有一个作用域,所以i也只有一个。循环结束,i变成了10。
数组中的函数执行后console.log(i)中的i始终指向全局作用域下的i。所以最后的输出是10。
如果变成let声明,则会产生多个作用域,每个作用域下的i都是独立的,都是新生成的变量,当进行访问时,会访问函数生成时的那个作用域里的i,结果则是我们希望的。
var a = [];
for (let i = 0; i < 10; i++) {
a[i] = function () {
console.log(i);
};
}
a[6](); // 6
有一点需要注意的是,for循环会生成一个父级作用域,它还有一个子级作用域。
for (let i = 0; i < 3; i++) {
let i = 'abc';
console.log(i);
}
// abc
// abc
// abc
我们知道大括号{}
会生成一个作用域,上面的例子中的let i=abc
就是在大括号中生成的,它跟for循环作用域下产生的i又不同,它是在循环体内产生的单独的子作用域。
let声明没有提升
由于原来的js编译设计机制问题,导致var声明会被提升到代码顶部,所以我们在声明变量之前也可以访问变量。
console.log(a)//undefined
var a=1
个中原理可以看这篇博客【你不知道的JavaScript】作用域是什么?
不过es6纠正了这个逻辑,使用let就不存在变量提升问题
console.log(a)//Uncaught ReferenceError: a is not defined
let a=10
一定要先声明后访问,因为先访问时,变量a是不存在的,所以报了一个查找不到的错误。
let声明有暂时性死区问题
只要块级作用域内存在let命令,那么就会牢牢地被绑定在这个区域,不受外部影响。
var a=123
function fn(){
console.log(a) //Uncaught ReferenceError: Cannot access 'a' before initialization
let a=888
}
上面代码中,虽然全局作用域下有个a,但是引擎已经预先知道内部有个let声明的a了,所以不会往外部获取全局下的a,而是先把函数作用域内的a锁死。
ES6明确规定,如果区块中存在let和const命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。
在代码块内,使用let命令声明变量之前,该变量都是不可用的。这在语法上,称为“暂时性死区”(temporal dead zone,简称 TDZ)。
暂时性死区的本质就是,只要一进入当前作用域,所要使用的变量就已经存在了,但是不可获取,只有等到声明变量的那一行代码出现,才可以获取和使用该变量。
有兴趣的同学还可以看看我写的这一篇JS薛定谔的变量提升
let声明不允许重复声明
在同一作用域下,let不允许声明两次同名的变量。
{
let a=0
let a=0
}
//Uncaught SyntaxError: Identifier 'a' has already been declared
不过var是可以的,新变量会覆盖老变量。
var a=0
var a=1
a //1
因此,不能在函数内部重新声明参数。
function func(arg) {
let arg;
}
func() // 报错
function func(arg) {
{
let arg;
}
}
func() // 不报错
let声明不会将变量挂到window顶层对象中
顶层对象,在浏览器环境指的是window对象,在 Node 指的是global对象。ES5 之中,顶层对象的属性与全局变量是等价的。
全局下let声明跟var声明所在的作用域并不算同一层,var所在的全局作用域会指向window对象,所以我们可以通过window来访问var声明的全局变量,但是let却不能通过window对象访问。
var a=10
window.a //10
let b=99
window.b //undefined
这是因为es6新设计了全局变量,纠正了原来全局变量会与window顶层对象挂钩的设计问题。
const声明
const声明跟let声明一模一样,唯一的区别就是const声明是一个常量,一旦定义,不能改变。
所以不能只声明不赋值
const a //Uncaught SyntaxError: Missing initializer in const declaration
赋值后,值不可改变。
const a=10
a=100 //Uncaught TypeError: Assignment to constant variable.
如果赋值给一个复杂对象,那么变量的值就是一个引用地址,只要不改变这个引用地址,还是可以修改复杂对象内部的属性的。
const a={name:'qiu'}
a.name='qiuyanxi'
a //{name:"qiuyanxi"}
a={name:"qiuyanxi"}//报错