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

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

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

  说明:由于有时候我们希望得知es6代码的具体实现原理或者说希望能够转化为es5使用,我们可以使用http://babeljs.io/来实现在线将es6代码转化为es5代码。

第一部分:let命令

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

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

   例1:

  代码如下所示: 

  1. {
  2. var a=5;
  3. let b=10;
  4. }
  5. console.log(a);
  6. console.log(b);

  我们在控制台得到的结果如下所示:

  也就是说,var声明的变量由于不存在块级作用域所以可以在全局环境中调用,而let声明的变量由于存在块级作用域所以不能在全局环境中调用。

 

  例2:这个例子是一个非常经典的例子。

  

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

  我们可以看到,两个例子中,唯一的区别是前者for循环中使用var来定义i,得到的结果是10.而后者使用的是let来定义i,最终得到的结果是6.这是为什么呢?阮一峰老师在书中的解释并不是很清楚,所以下面我会发表个人见解:

    关于这个问题,表面上确实不是很好理解,查询了很多资料,许多人讲到了很多晦涩难懂的知识,似乎很高大上,但是实际上并不难,下面根据我的理解进行解释,如有问题,欢迎批评指正,如果大家能够有些收获就再好不过了。

  例二前者(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:

  

  1. var num1=100;
  2. console.log(num1);
  3.  
  4. let num2=200;
  5. console.log(num2);
  6.  
  7. console.log(i);
  8. var i=10;
  9.  
  10. console.log(j);
  11. let j=5;

    我们可以看到结果如下:

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

说明:console.log(i);

   var i=10;

实际上相当于:

    var i;

    console.log(i);

   i=10;

所以会出现undefined的情况。

 

  三.暂时性死区

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

例5:

  1. var tmp=123;
  2. if(true){
  3. tmp="abc";
  4. let tmp;
  5. }

结果如下:

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

  

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

  

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

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

  

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

  1. if (true) {
  2. console.log(typeof x);
  3. let x;
  4. }

  这里如果没有let x,那么typeof x的结果是 undefined,但是如果使用了let x,因为let不存在变量提升,所以这里形成了暂时性死区,即typeof x也是会报错的。。。  从这里可以理解暂时性死区实际上就是这一部分是有问题的 。

  四.不允许重复声明

  

  1. function func (){
  2. let b=100;
  3. var b=10;
  4. }
  5.  
  6. function add(num){
  7. let num;
  8. return num+1;
  9. }
  10.  
  11. function another(){
  12. let a=10;
  13. let a=5;
  14. }

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

只是前两者为 b和num被声明过了。注意:第二个函数,虽然我们没有明确的声明,但是参数实际上是相当于用var声明的局部变量。

第二部分:const命令

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

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

  1. const a=10;
  2. a=100;

    结果如下

    

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

  1. const a;

     结果如下:

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

  1. if(true){
  2. const a=10;
  3. }
  4. console.log(a);

    结果如下:

    

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

  1. if(true){
  2. console.log(a);
  3. const a=10;
  4. }

结果如下:

  

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

  1. var a=10;
  2. const a=5;

结果如下:

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

  

  1. const a={};
  2. a.name="zzw";
  3. console.log(a.name);
  4.  
  5. const b=[];
  6. b.push("zzw");
  7. console.log(b);
  8.  
  9. const c={};
  10. c={name:"zzw"};

  结果如下:

因此,我们使用const所指向的地址不可变,但是地址的内容是可以变得。

    7.如果希望将对象本身冻结,可以使用Object.freeze()方法。

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

    于是通过Object.freeze()方法我们就不可以再改变对象的属性了(无效)。

ES6之let(理解闭包)和const命令的更多相关文章

  1. ES6学习笔记(1)----let和const命令

    参考书<ECMAScript 6入门>http://es6.ruanyifeng.com/ let和const命令 let 总结1.声明变量基本使用方法与var 相同  不同点  a.在代 ...

  2. 2. ES6基础-let和const命令

    目录 1. let命令 1.1 用法 1. 2 不存在变量提升 1.3 区域绑定 1.4 不允许重复声明 2. const命令 2.1 用法 2.2 与let类似的特性 2.3 const本质 2.4 ...

  3. ES6 之 let和const命令 Symbol Promise对象

    ECMAScript 6入门 ECMAScript 6(以下简称ES6)是JavaScript语言的下一代标准,已经在2015年6月正式发布了. (2016年6月,发布了小幅修订的<ECMASc ...

  4. es6 let和const命令(1)

    基本用法 ES新增了let命令,用于声明变量.其用法类似于var,但是所声明的变量只在let命令所在的代码块中有效. for(let i = 0;i<5;i++) {} console.log( ...

  5. es6之let和const命令的一些笔记

    let和const命令 let命令 基本用法 let命令用来声明变量,声明的变量只在命令所在的代码块内有效.for循环中很适合使用let命令. 有必要理解的例子: var a = []; for (v ...

  6. ES6之新增const命令使用方法

    hi,我又回来了,今天学习一下const命令. 声明一个常量 const声明一个只读常量,一旦声明,常量的值便不可改变. 例子如下: const food = 12; food = 23; // Un ...

  7. ES6中let与const命令详解

    阮一峰ES6入门 let 作用域 let命令用来声明变量,但声明的变量只在let命令所在的代码块内有效. { let a = 10; var b = 1; } a // ReferenceError: ...

  8. ES6学习之-let 和const命令

    let 和const命令 let命令 用来声明变量,类似于var .let声明的变量 只在let命令所在的代码块内有效. 在for循环里也是如此 每次循环其实都是一个代码块 function fn() ...

  9. ES6入门——let和const命令

    let和const命令 1.let命令 用法:类似于var,用来声明一个变量,区别是所声明的变量只在let命令所在的代码块内有效. let命令很适合用在for循环的计数器中,因为let声明的变量仅在作 ...

随机推荐

  1. WP8 对音视频格式支持的完整说明

    Supported media codecs for Windows Phone http://msdn.microsoft.com/en-us/library/windowsphone/develo ...

  2. Redis的五种数据结构

    Redis支持持久化只是它的一件武器,它提供了多达5种数据存储方式: 一  string(字符串) string是最简单的类型,你可以理解成与Memcached一模一样的类型,一个key对应一个val ...

  3. 别名现象,java对象之间的相互赋值

    请看一下代码 import java.util.*; class book{ static  int c = null; } public static void main(String[] args ...

  4. Webpack配置示例和详细说明

    /* * 请使用最新版本nodejs * 默认配置,是按生产环境的要求设置,也就是使用 webpack -p 命令就可以生成正式上线版本. * 也可以使用 webpack -d -w 命令,生成用于开 ...

  5. 1018MYSQL数据迁移到SQLSERVER

    -- 第一步利用MYSQL将数据结果的脚本迁移出来-- 第二步利用POWERDESGINER的反向功能,将脚本生成为物理模型 FILE-REVERSE DATEBASE -- 第三步将物理模型生成SQ ...

  6. lineNumber: 1; columnNumber: 1; 前言中不允许有内容

    周六项目还能运行,也没修改什么,周一来了启动项目,竟然报错了~~~ 这是遇到的错误提示: Cause: org.xml.sax.SAXParseException; lineNumber: 1; co ...

  7. MyBatis学习--延迟加载

    简介 在resultMap可以实现高级映射(使用association.collection实现一对一及一对多映射),association.collection具备延迟加载功能.例如:我们查询订单并 ...

  8. Java--剑指offer(9)

    41.输出所有和为S的连续正数序列.序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序 import java.util.ArrayList; public class Solution { ...

  9. MySQL的恢复脚本

    原文转自: http://blog.csdn.net/dbanote/article/details/13295727 应用场景: ********************************** ...

  10. Android点击EditText文本框之外任何地方隐藏键盘的解决办法

    1,实现方法一:通过给当前界面布局文件的父layout设置点击事件(相当于给整个Activity设置点击事件),在事件里进行键盘隐藏 <LinearLayout xmlns:android=&q ...