美文网首页
ES6学习系列(1):先来记住这些常用要点

ES6学习系列(1):先来记住这些常用要点

作者: huangyh_max | 来源:发表于2017-08-06 23:50 被阅读0次

    前言

    七月中旬,集中时间学习使用React框架的时候,接触到了很多ES6的语法。一开始看的时候,简直一脸蒙蔽,后面对看不懂的代码在google之后知道基本都是ES6的语法的使用。那么,我的学习领域肯定也得跟进扩展到ES6啦。现在,ES6也是前端工作者的基本标配了。
    当然,我们可以按照阮一峰大大的教程来详细学习,http://es6.ruanyifeng.com/
    但是考虑到时间成本,肯定先挑重点常用的来学习和记忆。
    七月底的时候整理了ES6的几个比较常用和重要的语法,今天修整发布下,当然是配合栗子来记录的。
    后面打算,对ES6的学习整理为一个系列,发布在博客记录,这是系列记录第一篇~嘻嘻。

    目前ES6的支持情况:

    使用gulp搭建一个ES6的开发转换环境,以作学习测试使用:
    https://segmentfault.com/a/1190000004394726?utm_source=APP&utm_medium=iOS&utm_campaign=socialShare

    let和const

    对于var,我们通过例子来知晓var的变量影响的范围:
    栗子1:
    这个例子可以看到var a变量提升到全局,同名变量会被覆盖:

    function aa(){
        var a=1;
        console.log(a); //1
        if(true){
            var a=2
        };
        console.log(a) //2
    }
    

    这是闭包中函数内的嵌套函数的使用情况下,嵌套函数内定义为var b的变量的变量提升程度的例子。可以看到,只提升到嵌套函数的顶部而已,没有再往上升一级。

    function test() {
        var b=1;
        function a() {
            var b = 2
            console.log('闭包中的b:',b)
          
        }
        console.log('全局变量的b',b)
        return a;
    }
    var result=test();
    result();
    

    配合这个例子看:

    function test() {
        var b=1;
        function a() {
          if (true) {
            var b = 2
            console.log(b)
          }
          return a;
        }
        console.log('b: ', b)
    }
    test() //b:1
    
    let

    let的用法和var类似,区别就是let所声明的变量,只在let命令所在的代码块内有效。也就是,let声明的变量不会提升,被锁在当前块。
    看下面一系列的例子:

    function test() {
        if(true) {
          console.log(a)//TDZ,俗称临时死区,用来描述变量不提升的现象
          let a = 1
        }
    }
    test()  //a is not defined
    
    function test() {
        if(true) {
          let a = 1
        }
         console.log(a)
    }
    test()  //a is not defined
    

    正确的使用就是,在let声明变量后,紧接着在同个作用域下使用:

    function test() {
        if(true) {
          let a = 1
          console.log(a)
        }
    }
    test() // 1
    
    const

    const是声明常量用的。声明之后的常量,是read only,而不能被赋值的,否则会报错。

    const  a=233;
    a=666;
    console.log(a)  //
    

    而如果我们用const来声明一个对象Object ,那么这种情况下,反而可以修改对象内属性的值。记住const常量修改自身的值不可以,而修改test.a的值可以:

    const test={
      a:1
    }
    test.a=2
    console.log(test)
    

    let和const的异同点:
    相同点:let和const都只在当前块的作用域内有效而已,不会变量提升。
    不同点:let声明的变量是可以重复赋值的,而const声明的常量不能被赋值。

    在这里延伸一道经典面试题:
    正常我们使用的for循环:

    for(var i = 0; i < 5; i++) {
        console.log(i) //0,1,2,3,4
    }
    

    如果在其中加入回调:

    for(var i = 0; i < 5; i++) {
      setTimeout(() => {
        console.log(i) //5, 5, 5, 5, 5
      }, 0)
    }
    console.log(i) //5 i跳出循环体污染外部函数
    

    将var改成let之后

    
    for(let i = 0; i < 5; i++) {
      setTimeout(() => {
        console.log(i) // 0,1,2,3,4
      }, 0)
    }
    console.log(i)//i is not defined i无法污染外部函数
    
    应用:

    当希望变量保证不被恶意修改,使用const。例如在react中,props传递的对象是不可更改的,所以使用const声明;
    声明一个对象的时候,推荐使用const;
    当需要修改声明的变量值时,使用let,var能用的场景都可以使用let替代。

    解构赋值

    函数的扩展

    rest参数

    rest参数就是“扩展运算符...”。

    let a=[1,2,3];
    let [c,...d]=a;
    console.log(c) //1
    console.log(d) //2,3
    

    注意:rest参数只能作为最后一个参数,之后不能有其他参数。否则会报错:

    let a=[1,2,3];
    let [...d,c]=a;
    
    console.log(d) //报错:SyntaxError:Rest element must be last element in array
    

    函数的length属性,不包括rest参数
    (function(a){}).length //1
    (function(a,...b){}).length //1

    一个我觉得很好的数组push的例子:

    function push(array,...items){
      items.forEach(function(item){
         array.push(item);
         console.log(item);
      });
      console.log(array);
    }
    var a=[];
    push(a,1,2,3) 
    //1,2,3
    //[1,2,3]
    

    另一个使用res参数来取代arguments对象的例子:

    const pa=(...args) =>{
       console.log(args)
       return args.reduce((pre,cur)=>{
         return pre+cur;
       },0)
    };
    pa.apply(this,[1,2,3,4]) //10
    

    在这个例子中做一个知识点延伸就是reduce()方法的使用。
    Array.reduce(callback,[initialValue])
    就是先得是一个数组来使用这个reduce()方法,然后reduce方法会对数组中的每一个元素都调用一次callback函数。initialValue是可填项,如果填了的话,就传入给callback函数中,作为累加的初始项。
    而callback函数就是比如:

    function add(first,second){
       return first+second;
    }
    

    学了这个reduce方法后,以后可以替代使用for循环来做累加的操作了。

    所以这个例子就等同于:

    const pa=function(...args){
       console.log(args)
       return args.reduce(function(pre,cur){
         return pre+cur;
       },0)
    }
    pa.apply(this,[1,2,3,4])
    
    箭头函数

    学习这个箭头函数,只需要谨记:

    var f=v =>v;
    

    等同于

    var f=function(v){
       return v;
     };
    

    如果函数没有参数或有多个参数时,那么就需要写一个圆括号()

    var a=()=>5;
    //等同于
    var a=function(){
       return 5;
    }
    
    var sum=(a,b) =>{
      return a+b;
    }
    //等同于
    var sum=function(a,b){
       return a+b;
    }
    
    const s=function(){
       return {
           a:'hello world'
       }
    }
    

    等价于

    const s=()=>{
      return {
           a:'hello world'
       }
    }
    

    就等于后面用{}括住的代码块,直接就是function()后面跟着这一块,不用用{}再括第二次。

    箭头函数这块,我觉得阮一峰大大的教程里的例子就非常能让人理解了,直接看他的说明教程中的例子就行。具体摘抄来看下:
    箭头函数使用起来很简洁对不对,当我们用在回调函数里的时候,就更觉得简洁了。
    看这个例子:

    [1,2,3].map(function(x){
       return x*x;
    })
    

    如果是用箭头函数来写:

    [1,2,3].map(x=>x*x)
    

    哎呀,好简洁对不对~~~~
    这个例子,我看了又可以延伸一个知识点了:map()方法的使用。
    map()方法在MDN上的解释:对数组的每个元素调用定义的回调函数,并返回包含结果的数组。
    这样写言简意赅,一目了然这个方法的用法。
    就是把[1,2,3]这个数组中的每个元素,执行x*x,得到相应的结果,组成一个数组返回,结果就是[1,4,9]

    箭头函数中this的指向

    重点要说箭头函数的this指向。从此不用再使用var _this=this的用法来重新修改this的指向了。
    所以有人会说箭头函数,是一种语法糖,因为使用箭头函数时,使用者就不用去确认不用情形使用下this的指向了。
    具体也是看阮一峰教程的例子来理解:
    函数体内的this的指向,是定义时所在的对象

    function foo() {
      setTimeout(() => {
        console.log('id:', this.id);
      }, 100);
    }
    var id = 21;
    foo.call({ id: 42 });
    // id: 42
    

    之前学this篇的时候,我写的博客中专门说了,setTimeout()方法和 setInterval()中this的指向是全局对象window,尽管如果setTimeout()方法是在dom元素的事件下使用的,也不会指向dom元素对象本身。
    所以这个例子中,按这个道理,setTimeout()中的this是指向全局对象window的,那this.id按理应该是等于全局的id变量21才对。但是使用箭头函数时,this的指向指向了foo这个函数对象。所以this.id对应的就是通过call()方法传入的id的值42,输出为id:42

    看阮一峰教程里写的两个对比例子:

    function Timer() {
      this.s1 = 0;
      this.s2 = 0;
      // 箭头函数
      setInterval(() => this.s1++, 1000);
      // 普通函数
      setInterval(function () {
        this.s2++;
      }, 1000);
    }
    
    var timer = new Timer();
    
    setTimeout(() => console.log('s1: ', timer.s1), 3100);
    setTimeout(() => console.log('s2: ', timer.s2), 3100);
    // s1: 3
    // s2: 0
    

    一样道理, setInterval()方法是箭头函数的写法的话,就指向定义所在的Timer函数对象,那么this.s1就取this.s1=0的值。而setInterval()函数是普通写法的话,尽管是包裹在Timer函数下定义的,但this指向运行时所在的作用域(即全局对象)。但是我们看全局下没有timer.s2的定义值,所以就一次都不更新。所以,3100毫秒之后,timer.s1被更新了3次,而timer.s2一次都没更新。

    再看这个DOM 事件的回调函数封装在一个对象里面的例子,对回调函数使用箭头函数的写法的好处:
    正常使用ES5的写法,我们需要使用var _this=this,才能在document对象的点击事件的函数内调用doSomething这个属性。

    var handler = {
      id: '123456',
      init: function() {
         var _this=this
        document.addEventListener('click',
          function(event){
              _this.doSomething(event.type), false);
          }
      },
      doSomething: function(type) {
        console.log('Handling ' + type  + ' for ' + this.id);
      }
    };
    

    现在换成箭头函数的写法,我们就可以摒弃_this的借位写法了:本来init()方法中的this.doSomething这一行的this是会指向document对象的,但使用箭头函数后是指向handler对象了。

    var handler = {
      id: '123456',
    
      init: function() {
        document.addEventListener('click',
          event => this.doSomething(event.type), false);
      },
    
      doSomething: function(type) {
        console.log('Handling ' + type  + ' for ' + this.id);
      }
    };
    

    class的使用:取代构造函数实例化得到对象的做法

    基本使用

    在JavaScript中,我们面向对象的写法,在ES5规范下,都是使用构造函数,然后实例化这个构造函数,然后来得到对象的。现在ES6引入了其他语言中的class概念来实现面向对象,来等价于我们以往习惯的构造函数的写法:
    例如:下面这个构造函数:

    funtion constr(){
        this.a=1;
        this.b=2;
    }
    constr.prototype.init=function(){
       console.log(this.b)
    };
    var sum=new constr();
    sum.init;
    

    可以使用class语法,等价转化为:

    class constr {
      constructor() {
        this.a=1;
        this.b=2;
      }
      init() {
        console.log(this.b);
      } //注意有个括号
    }
    let sum  = new constr ();
    sum.m()  //2
    
    继承

    Class通过extends关键字实现继承,和ES5中的原型链继承不一样,相对简洁很多。
    例如:

    class Point{
    }
    class ColorPoint extends Point{
    }
    

    子类ColorPoint 继承了父类Point的所有属性和方法,等于就是把父类Point中的所有属性方法都复制到子类ColorPoint 中

    重点是要结合super()关键字使用。记住下面的话:子类必须在constructor方法中调用super方法,否则新建实例时会报错。

    class Point{
    }
    class ColorPoint extends Point{ 
        constructor(){
           
        }
    }
    let cp=new ColorPoint (); //ReferenceError
    

    因为子类没有自己的this对象,而是继承父类的this对象,然后对其加工的。如果不用super()方法,那么子类就得不到this对象。

    再说一点,在子类的构造函数中,只有调用super之后才能使用this关键字,否则报错。
    在下面这个例子中,子类的constructor方法没有调用super之前,就使用this关键字,结果报错,而放在super方法后的this.color就正确。

    class Point {
      constructor(x, y) {
        this.x = x;
        this.y = y;
      }
    }
    
    class ColorPoint extends Point {
      constructor(x, y, color) {
        this.color = color; // ReferenceError
        super(x, y);
        this.color = color; // 正确
      }
    }
    

    最后来说下,生成子类实例的写法,也是一样使用new的方式来生成实例,传入数值:

    let cp=new ColorPoint (25,8,'green')
    

    然后看例子可知,cp这个实例继承自子类,也继承自父类:

    cp instanceof ColorPoint  //true
    cp instanceof Point //true
    
    super关键字的使用

    super关键字,当做函数使用和当做对象使用,两种情况下的用法完全不同。
    这点待我补充。

    Module

    JavaScript是一直没有模块(module)体系的,在浏览器端没办法实现I/O。而前端工作者们为了适应大型复杂的前端项目所需的模块化需求,逐渐发展出前端模块化的规范:commonJS、AMD和CMD。(这一块的知识,我也专门写了一篇前端模块化规范发展的记录博客)。这些规范,无非都是使用import、require、export这一类的方法,来讲一个大程序拆分成相互依赖的小文件。
    而到ES6,就终于实现了模块功能,等于JavaScript语法上就原生支持模块的实现方式了,而不必再依赖框架(例如requireJS框架)来转换过渡了。
    ES6的Module有两个关键字import和export。export是将本身的属性方法给暴露出去,给其他模块使用。而一个模块想使用其他模块中的属性方法时,就使用import
    具体看栗子:
    在lean.js中将属性a的值传递出去:

    export let a=1;
    

    然后我们想在init.js模块中使用a的值:

    import {a} from 'lean.js';  //或具体为lean.js所在的文件路径
    console.log(a) //1
    

    这其中实现的本质是,前端是无法实现I/O的,所以import和export的使用,就是需要借助webpack或gulp这样的配置环境,使用babel.js插件,将其转化为commonJS中的require,在node.js服务端运行的。

    Promise

    下一篇继续讲这个知识点。

    Generator

    下一篇继续讲这个知识点。

    相关文章

      网友评论

          本文标题:ES6学习系列(1):先来记住这些常用要点

          本文链接:https://www.haomeiwen.com/subject/drwtlxtx.html