Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

let、const与var的区别 #11

Open
18888628835 opened this issue Apr 6, 2021 · 0 comments
Open

let、const与var的区别 #11

18888628835 opened this issue Apr 6, 2021 · 0 comments

Comments

@18888628835
Copy link
Owner

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"}//报错
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant