ECMAScript6--let,const和块级作用域

137 阅读6分钟

let

let可以看做一种更完美的var,var本身是有缺陷的。所以JS的作者又创建了一个更完美的关键字let。

let的作用

**let可以产生块级作用域。**所谓块级作用域我是理解成和函数作用域一致的局部作用域。

块级作用域的形成和范围

函数作用域的产生和范围就是函数声明形成的,范围就是函数内部这个区域。那么块级作用域怎么产生呢?它可以通过我们的关键字let/const来产生块级作用域。它的作用范围就是一个{}离开大括号访问就会报错 我个人觉得当你很好的理解了我前文说过的函数作用域,其实理解块级作用域就是一个水到渠成的事情。

let/const和var的其他异同

  • 前文我们说过,如果使用var去声明变量,会存在一个声明提升的现象。但是使用let/const不会存在这个现象。
 <script>
      console.log(a);//报错
      console.log(b);//报错
      let a = 1;
      const b = 2; 
    </script>
  • const 声明常量的时候必须赋值才行。不能对他进行再次赋值。const b;b=1;//报错
  • 和var不同,let和const都不能重复声明。

接下来我想去探究一下一件挺有意思的事情也是困扰我一会。在网上看到一篇大神写的文章才恍然大悟。

for循环中的let

先举个例子

var a = [];
for(let i = 0;i < 3;i++)
{
a[i]=function()
{
console.log(i);
}
}
a[0]();//0
a[1]();//1
a[2]();//2

有个问题不知道大家有没有想过,为什么let 关键字只执行了一次,为什么会产生三个块级作用域呢??

  • 猜想一for循环的初始条件let i = 0执行了多次 通过打断点的方式发现声明确实只执行了一次
  • 猜想二 JS底层在每次循环过程中给我们加塞了let声明

**问题二,既然每次循环对应的都是一个块级作用域(比如这里应该产生了三个块级作用域),那么下一次循环的i值是要基于上一次循环的i值计算得来的啊,那这个是怎么实现的呢?**他们俩之间应该是相互不可见的呀。这个问题阮一峰老师进行了解答:(ES6入门文章中) **JavaScript 引擎内部会记住上一轮循环的值,初始化本轮的变量i时,就在上一轮循环的基础上进行计算。**也就是说JS内部引擎帮我们实现了,偷偷帮我们做了这件事儿,真令人感动。
好的问题二得到了解决我们接着回到问题一。现在我们来实现一下我们的猜想,模拟一下底层是如何实现每次循环都形成一个块级作用域的,接下来的东西只是一个猜想

  var a = [];
    {//父级作用域
        let i = 0;//先执行for循环的初始条件
        if(i < 3)//进行条件判断
        {
             let k = i;           //执行循环体,由于这里是块级作用域,我们猜想内部可能就是又使用了一次let定义了一个新变量,达到这种效果
             a[k]=function()
             {
                 console.log(k);//循环体内部所有的i都委托给k了
             }
        }
        i++;//再执行for循环的表达式3
        if(i < 3)//然后是不是又进行条件判断呢对吧
        {
            let k = i;           
             a[k]=function()
             {
                 console.log(k);
             }
        }
        i++;
        if(i < 3)
        {
            let k = i;           
             a[k]=function()
             {
                 console.log(k)
             }
        }

    }
    a[0]();//0
    a[1]();//1
    a[2]();//2

我们猜测JS引擎在给我们解释for(let i = 0;i < 3;i++)循环的时候大致情况可能是这样才导致了形成了多个块级作用域。当然我觉得上面的代码用while来写更具说服力,但是我们这个只是模拟并不是要完全实现for循环。while方式的模拟:

var a = [];
    {
        let i = 0;
      
        while(i < 3)
        {
            let k = i;           
             a[k]=function()
             {
                 console.log(k);
             }
             i++;
        }


    }
    a[0]();//0
    a[1]();//1
    a[2]();//2

虽然感觉用循环去模拟循环有点蠢,但是这样感觉更贴近实际情况。
那现在我们来思考一个问题:

var a = [];
    for(let y = {i:0};y.i < 3;y.i++)
    {
        a[y.i] = function()
        {
            console.log(y.i);
        }
    }
    a[0]();//?
    a[1]();//?
    a[2]();//?

请问这三个地方应该是输出多少呢?这里其实输出的是3 3 3。那这个时候你就很懵了。不是说好的是let形成块级作用域嘛?为什么还会是3,3,3呢
原因:我觉得这个地方得涉及到浅拷贝和深拷贝的知识了,我们还是可以通过我们设想的那个模拟代码入手。

var a = [];
let y = {i:0}//执行for循环表达式1
if(y.i < 3)//执行for循环表达式2
{
let k = y;//执行循环体,那么这个时候,y在每次循环体中,应该都是独立存在的,形成了多个块级作用域,所以我们应该这样模拟,让k作为y在本次循环中的代理,也就是说循环体里面的所有y都被k替换掉了
a[k.i]=function()
{
console.log(k.i)
}
y.i++//执行for循环表达式3这时候已经跳出for循环体了,所以是y不是k。
}

这样执行会导致什么结果呢?虽然每个循环体里面的y(也就是k)有点儿绕,都是独立的个体,都形成了块级作用域,但是他们共同指向同一个对象,就是刚开始定义的let y = {i:0},所以最后导致每个块级作用域里面的y都是同一个对象。再去获取它的i属性自然都是一样的i。(每个循环体里面的y也就是k,在被赋值的时候,是深拷贝,因为是对象,所以传递的是地址。)再来解释之前,为什么不会出现这种现象呢?因为之前是普通变量之间的赋值,是浅拷贝,它只是把值传递了过去,并没有传递地址。

总结

这个地方主要是探究了两个问题:

  • 为什么let 只声明了一次,但是形成了很多个块级作用域
  • 每个块级作用域的数据都是独立存在在自己空间的,为什么下一次循环的初始值,会基于上一次循环的结束值进行计算。

与此同时我们也进行了大胆的猜想,从目前的角度分析我们的这种猜想和模拟应该不存在什么很大的问题。也算是弄清楚了这个比较基本的问题。也谢谢www.cnblogs.com/echolun/p/1… 文章的作者给我的帮助。