ES6中的let和const

我心飞翔 分类:javascript

简介

好像又很久没有写文章了,说实话,写文章这种东西是真的看心情,有时候想写,有时候又不想写,就和过山车差不多哈哈哈,今天我们也不讲深奥的,就讲讲我们在变量声明时很常见的 let 和 const。

既然讲到 let 和 const,我们就得摸着自己的良心问一下自己,这是什么,为啥出现,它为我们解决了什么样的问题。再此之前,让我们看看在 ES6 之前 var 到底遗留了什么问题给我们。

var 迷惑操作锦集

同一作用域可重复声明变量

var a = 1;
var a = 2;
console.log(a);//2

//可以理解成这样
var a;
a = 1;
a = 2;
console.log(a);
 

对于这段代码,有人想发表什么意见不。没有啊,我觉得没什么问题啊,很好啊。emmmmm,好像是没什么问题哦,但是当我们需要外部加载多个js脚本的时候,才会突然发现,咦,我这个变量的值怎么和我心里想的不一样,然后开始找bug,抓头发,花了1分钟,一个小时,或者是一天,才知道原来是其他的js脚本也声明了这个变量,把我的变量值给偷偷的改掉了。这还是在js脚本是自己写的情况下,要是其他人写的呢,不得把我坑死。这时候我们才会开始吐槽,为啥声明相同变量浏览器不给我报错啊!心疼自己头发几分钟。

变量提升带来的问题

奇怪的数据访问方式

if(Math.random() < 0.5){
    var a = 1;
    console.log(a);
}else{
    var a = 2;
    console.log(a);
}
console.log(a);

//可以理解成这样
var a;
if(Math.random() < 0.5){
    a = 1;
    console.log(a);
}else{
    a = 2;
    console.log(a);
}
console.log(a);

//另一个例子
console.log(b);//undefined
var b = 1;

//也可以理解成这样
var b;
console.log(b);
b = 1;
 

眼尖的小伙伴会说,为啥外面的 console 能读取到变量 a哦,我明明没有在外面声明变量 a,我还蛮期待它会报错的呢。???还有就是为啥能在 b变量声明前就能读到 b啊,这不合常理。

闭包问题

for(var i = 1; i <= 10; i++ ){
    var btn = document.createElement("button");
    btn.innerText = "按钮" + i;
    btn.onclick = function(){
        console.log(i);
    }
    document.body.appendChild(btn);
}

console.log(i);//11

//也可以这样理解
var i;
var btn;
for(i = 1; i <= 10; i++ ){
    btn = document.createElement("button");
    btn.innerText = "按钮" + i;
    btn.onclick = function(){
        console.log(i);
    }
    document.body.appendChild(btn);
}
 

这段代码就需要你们在自己的编辑器上运行了,你会发现问题的,嘿嘿。

污染全局对象成员

var a = 1;
console.log(window.a, window.a === a);//1 true

var console = 1;
console.log(console);//console.log is not a function
 

直接报错了,还有这种操作,妙啊妙啊。

上面奇奇怪怪的问题大家应该都知道原因吧,不是吧不是吧,都2021年了,不会有人还不知道把。没有贬义的意思,单纯调侃一下。实在不知道的,可以自己百度搜索一下,随便锻炼你们的动手能力(当然有一部分自己懒的原因,我反思一下),而且今天的重点是 let 和 const。

没错,他们的出现解决了以上几个问题。

let

let允许你声明一个作用域被限制在 块级中的变量、语句或者表达式。与 var 关键字不同的是,var声明的变量只能是全局或者整个函数块的。var 和 let 的不同之处在于后者是在编译时才初始化。

这是取自于 MDN(Mozilla Developer Network) 中对 let 的一段描述。是不是有点看不懂,其实说白了就是 let 关键字可以将变量绑定到所在的任意作用域中(通常是 { .. } 内部)。换句话说,let 为其声明的变量隐式的绑定了所在的块作用域。怎么理解呢,用之前的代码来解释把。

if(Math.random() < 0.5){
    let a = 1;
    console.log(a);
}else{
    let a = 2;
    console.log(a);
}
console.log(a);//a is not defined
 

这个时候你会发现外面的 console 已经读取不到变量 a了,为啥呢,因为变量 a被困在在这个{ .. }里面了,俗称块级作用域,它不会再提升到外面的全局作用域中。

总结:let声明的变量只在其声明的块或子块中可用,这一点,与var相似。二者之间最主要的区别在于var声明的变量的作用域是整个封闭函数。

用这个知识再来讲讲上面的闭包问题

//在没有let之前,可以用立即执行函数来解决
for(var i = 1; i <= 10; i++ ){
    var btn = document.createElement("button");
    btn.innerText = "按钮" + i;
    (function(i){
        btn.onclick = function(){
            console.log(i);
        }
    })(i);
    document.body.appendChild(btn);
}

//用let解决更方便
for(let i = 1; i <= 10; i++ ){
    let btn = document.createElement("button");
    btn.innerText = "按钮" + i;
    btn.onclick = function(){
        console.log(i);
    }
    document.body.appendChild(btn);
}

console.log(i);//i is not defined
 

不知道看到这里,你们有没有动手实践过上面的例子,现在我不装了,我摊牌了。之前的结果是不过你点哪个按钮,打出的结果都是11,为啥呢,为啥呢?现在就容许我,这个前端新手给你们解释一下。

因为当我们点击按钮触发点击事件的时候,里面的循环早已经执行完了,由于 var声明的变量提升问题,执行点击函数的时候,读取到的变量 i其实就是全局环境下的变量 i,而这时候的 变量 i 一直都是11,所以无论你点哪个按钮,打印出来的都是11。那为啥 let声明的变量就可以解决这个问题呢?那是因为每进入一次新的循环体,let 声明的变量 i都会隐式的绑定该循环体中的作用域,这样不同的按钮就都会有各自独自的块级作用域,就很好的解决了上面奇奇怪怪的问题。

至于 let不允许在同一作用域下声明相同变量,不会污染全局对象成员,这个相对来说就比较简单,就真的靠你们自己自觉动手了啊。

不过还有一个比较有意思的例子如下:

function do_something() {
  console.log(foo); // ReferenceError: Cannot access 'foo' before initialization
  let foo = 2;
}

do_something();
 

通过上面的讲解,我们得知,let声明的变量不会提升,所以报错的话那也很正常,不过这个报错的信息怎么和我们想的不一样,不应该是foo is not defined嘛,但这里的报错却是Cannot access 'foo' before initialization,意思就是不能在初始化前访问 foo变量。其实这里又会有一个新的知识点,那就是暂时性死区,实际上foo变量声明还是提升了,只不过被放入了暂时性死区,当访问暂时性死区里面的变量,就会报上面的错误,而只有声明了该变量,才能从暂时性死区里面拿出来,进行正常访问。在MDN中是这么说的:

通过 let 声明的变量直到它们的定义被执行时才初始化。在变量初始化前访问该变量会导致 ReferenceError。该变量处在一个自块顶部到初始化处理的“暂存死区”中。

const

const 和 let基本有一样的效果,唯一的区别只是 const声明的变量必须在初始化阶段就进行赋值操作,并且不能被重复赋值。这意味了 const一般用来声明一个常量。

const a;//SyntaxError: Missing initializer in const declaration

const b = 1;
b = 2;//TypeError: Assignment to constant variable.
 

但是这里要注意一下,这里说的不能改变指的是声明的变量所持有的值是不可变的,如果持有的值是一个引用对象地址,那么该引用对象内部的内容还是可以改变的。如下:

const obj = {
    name: "xxx",
    age: 18,
}

obj.name = "aaa",
console.log(obj);//{name: "aaa", age: 18}
 

结语

今天到这里就讲完了,没想到我们平时这么常见的声明语句也有这么多学问在里面,那么今天就到这里啦,谢谢各位品读,下次光临。

回复

我来回复
  • 暂无回复内容