ES6之let(理解闭包)和const命令

ES6之let(理解闭包)和const命令

  最近作项目的过程当中,使用到了ES6,由于以前不多接触,因此使用起来还不够熟悉。所以购买了阮一峰老师的ES6标准入门,在此感谢阮一峰老师的著做。javascript

  咱们知道,ECMAScript 6即ES6是ECMAScript的第五个版本,由于在2015年6月正式发布,因此又成为ECMAScript2015。ES6的主要目的是为了是JS用于编写复杂的大型应用程序,成为企业级的开发语言。html

  说明:因为有时候咱们但愿得知es6代码的具体实现原理或者说但愿可以转化为es5使用,咱们可使用http://babeljs.io/来实如今线将es6代码转化为es5代码。java

第一部分:let命令

  一.块级做用域(重点)。

   咱们知道,在javascript中只有全局做用域和函数做用域,并不存在块级做用域。这样,在使用时就会出现一些问题。 下面咱们先来举例说明let块级做用域的使用。es6

   例1:数组

  代码以下所示: 安全

        {
            var a=5;
            let b=10;
        }
        console.log(a);
        console.log(b);

  咱们在控制台获得的结果以下所示:babel

  也就是说,var声明的变量因为不存在块级做用域因此能够在全局环境中调用,而let声明的变量因为存在块级做用域因此不能在全局环境中调用。闭包

 

  例2:这个例子是一个很是经典的例子。函数

  

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

 

  咱们能够看到,两个例子中,惟一的区别是前者for循环中使用var来定义i,获得的结果是10.然后者使用的是let来定义i,最终获得的结果是6.这是为何呢?阮一峰老师在书中的解释并非很清楚,因此下面我会发表我的看法:spa

    关于这个问题,表面上确实不是很好理解,查询了不少资料,许多人讲到了不少晦涩难懂的知识,彷佛很高大上,可是实际上并不难,下面根据个人理解进行解释,若有问题,欢迎批评指正,若是你们可以有些收获就再好不过了。

 

 

  例二前者(var i)具体执行过程以下:

var a=[];


var i=0;//因为var来声明变量i,因此for循环代码块不具有块级做用域,所以i认为是全局变量,直接放在全局变量中。
a[0]=function(){
    console.log(i);//这里之因此i为i而不是0;是由于咱们只是定义了该函数,未被调用,因此没有进入该函数执行环境,i固然不会沿着做用域链向上搜索找到i的值。
}// 因为不具有块级做用域,因此该函数定义就是全局做用域。


var i=1;//第二次循环,这时var i=1;覆盖了前面的var i=0;即如今i为1;
a[1]=function(){
    console.log(i);//解释同a[0]函数。
}


var i=2;// 第三次循环,这时 i=2,在全局做用域中,因此覆盖了前面的i=1;
a[2]=function(){
    console.log(i);
}


......第四次循环 此时i=3  这个以及下面的i不断的覆盖前面的i,由于都在全局做用域中
......第五次循环 此时i=4
......第六次循环 此时i=5
......第七次循环 此时i=6
......第八次循环 此时i=7
......第九次循环 此时i=8   


var i=9;
a[9]=function(){
    console.log(i);
}


var i=10;// 这时i为10,由于不知足循环条件,因此中止循环

紧接着在全局环境中继续向下执行。

       a[6]();//这时调用a[6]函数,因此这时随即进入a[6]函数的执行环境,即a[6]=function(){console.log(i)};执行函数中的代码 console.log(i); 由于在函数执行环境中不存在变量i,因此此时会沿着做用域链向上寻找(可参考个人博文《深刻理解做用域和做用域链,即进入了全局做用域中寻找变量i,而全局做用域中i=10覆盖了前面全部的i值,因此说这时i为10,那么a[6]的值就是10了。

  

  说明:对于例如a[1]=function(){console.log(i)};而不是a[1]=function{console.log(1)},能够在控制台中输出a[1]函数,便可获得验证。

 

 

     例二后者(let i)具体执行过程以下:

 


var a=[];//建立一个数组a;

{ //进入第一次循环
    let i=0; //注意:由于使用let使得for循环为块级做用域,这次let i=0在这个块级做用域中,而不是在全局环境中。
    a[0]=function(){
        console.log(i);
     }; //注意:因为循环时,let声明i,因此整个块是块级做用域,那么a[0]这个函数就成了一个闭包。
}// 声明: 我这里用{}表达并不符合语法,只是但愿经过它来讲明let存在时,这个for循环块是块级做用域,而不是全局做用域。

 

讲道理,上面这是一个块级做用域,就像函数做用域同样,函数执行完毕,其中的变量会被销毁,可是由于这个代码块中存在一个闭包,闭包的做用域链中包含着(或着说是引用着)块级做用域,因此在闭包被调用以前,这个块级做用域内部的变量不会被销毁。(更多闭包知识,能够看个人博文《JavaScript之闭包》)


{ //进入第二次循环
     let i=1; //注意:由于let i=1; 和 上面的let i=0;出在不一样的做用域中,因此二者不会相互影响。
     a[1]=function(){
         console.log(i);
     }; //一样,这个a[i]也是一个闭包
}

......进入第三次循环,此时其中let i=2;
......进入第四次循环,此时其中let i=3;
......进入第五次循环,此时其中let i=4;
......进入第六次循环,此时其中let i=5;
......进入第七次循环,此时其中let i=6;
......进入第八次循环,此时其中let i=7;
......进入第九次循环,此时其中let i=8;

{//进入第十次循环
    let i=9;
    a[i]=function(){
        console.log(i);
    };//一样,这个a[i]也是一个闭包
}

{
    let i=10;//不符合条件,再也不向下执行。因而这个代码块中不存在闭包,let i=10;在此次循环结束以后难逃厄运,随即被销毁。
}

a[6]();//调用a[6]()函数,这时执行环境随即进入下面这个代码块中的执行环境:funcion(){console.log(i)};

{
     let i=6;
     a[6]=function(){
          console.log(i);
     }; //一样,这个a[i]也是一个闭包
}

    a[6]函数(闭包)这个执行环境中,它会首先寻找该执行环境中是否存在 i,没有找到,就沿着做用域链继续向上到了其所在的代码块执行环境,找到了i=6,因而输出了6,即a[6]();的结果为6。这时,闭包被调用,因此整个代码块中的变量i和函数a[6]()被销毁。

 

相信你们仔细看完上面的函数执行的过程,对let var 块级做用域 闭包就有一个很好的理解了。我认为重要的是对于函数执行过程的理解!

 

 

  二.不存在变量提高

  这里是说使用let不会像使用var同样存在一个变量提高的现象。变量提高是什么呢?在没有接触es6以前我对此也不清楚,可是我想你们必定都据说过函数声明提高:函数声明来定义函数便可实现函数声明提高,这样,咱们能够先调用函数,后声明函数;而函数表达式方法不会实现函数声明提高,这样,若是先调用函数,后声明函数,则会抛出错误!!(对于函数声明提高更多知识能够看个人博文《JavaScript函数之美~》)。  那么能够以此类推,var定义变量:能够先使用,后声明;而let定义变量:只可先声明,后使用。

  例3:

  

        var num1=100;
        console.log(num1);

        let num2=200;
        console.log(num2);

        console.log(i);
        var i=10;

        console.log(j);
        let j=5;

    咱们能够看到结果以下:

即前两个都是先声明后使用,没有问题。然后两个都是先使用,后声明,用var 声明的显示undefined,而 let声明的直接报错。

说明:console.log(i);

   var i=10;

实际上至关于:

    var i;

    console.log(i);

   i=10;

因此会出现undefined的状况。

 

 

  三.暂时性死区

  暂时性死区即:只要一进入当前做用域,所要使用的变量就已经存在,可是不可获取,只有等到声明变量的那一行代码出现,才能够获取和使用该变量。

例5:

 

    var tmp=123;
    if(true){
        tmp="abc";
        let tmp;
    }

 

   结果以下:

也就是说:虽然上面的代码中存在全局变量tmp,可是块级做用域内let又声明了一个局部变量tmp,致使后者绑定了块级做用域,因此在let声明变量前,对tmp赋值会报错。此即暂时性死区。

  

  注意:ES6规定暂时性死区和不存在变量提高就是为了减小运行时的错误,防止在变量声明前就使用这个变量,从而致使意料以外的行为。

  

  暂时性死区就是: 只要块级做用域内存在let,那么他所声明的变量就绑定了这个区域,再也不受外部的影响。

  暂时性死区即 Temperary Dead Zone,即TDZ。 

  

       注意:暂时性死区也意味着 typeof 再也不是一个百分之百安全的操做。  以下:

    if (true) {
      console.log(typeof x);
      let x;
    }

  这里若是没有let x,那么typeof x的结果是 undefined,可是若是使用了let x,由于let不存在变量提高,因此这里造成了暂时性死区,即typeof x也是会报错的。。。  从这里能够理解暂时性死区实际上就是这一部分是有问题的 。

 

 

 

  四.不容许重复声明

  

    function func (){
        let b=100;
        var b=10;
    }

    function add(num){
        let num;
        return num+1;
    }

    function another(){
        let a=10;
        let a=5;
    }

上述三个获得的结果均为:

只是前二者为 b和num被声明过了。注意:第二个函数,虽然咱们没有明确的声明,可是参数其实是至关于用var声明的局部变量。

 

 

 

第二部分:const命令

      什么使const命令呢?实际上它也是一种声明常量的方式。const命令用来声明常量,一旦声明,其值就不能改变。初次以外,const和let十分类似。也就是说前者是用于声明常量的,后者是用于声明变量的。

 

    1.const声明常量,一旦声明,不可改变。

    const a=10;
    a=100;

    结果以下

    

    2.既然const一旦声明不可改变,因此在声明时必须初始化。

const a;

     结果以下:

 

    3.const所在的代码块为块级做用域,因此其变量只在块级做用域内使用或其中的闭包使用。

    if(true){
        const a=10;
    }
    console.log(a);

    结果以下:

 

    

    4.const声明的变量不存在变量提高。

    if(true){
        console.log(a);
        const a=10;
    }

结果以下:

  

 

    5.const不可重复声明常量。

    var a=10;
    const a=5;

结果以下:

 

 

    6.const命令只是保证了变量名指向的地址不变,并不保证该地址的数据不变。

  

    const a={};
    a.name="zzw";
    console.log(a.name); 

    const b=[];
    b.push("zzw");
    console.log(b);

    const c={};
    c={name:"zzw"};

  结果以下:

所以,咱们使用const所指向的地址不可变,可是地址的内容是能够变得。

 

    7.若是但愿将对象自己冻结,可使用Object.freeze()方法。

    const a=Object.freeze({});
    a.name="zzw";
    console.log(a.name); //undefined

    因而经过Object.freeze()方法咱们就不能够再改变对象的属性了(无效)。