美文网首页
ES6几点知识

ES6几点知识

作者: 海娩 | 来源:发表于2017-02-26 17:23 被阅读0次

    本篇文章记录的是ES6中的letclassextendsuper,这几个与react编写过程遇到的知识
    参考:阮一峰ES6

    let命令

    它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效。例如在for循环就适合用let
    ES6 的模块自动采用严格模式,不管你有没有在模块头部加上"use strict";。

        {
            let a = 10;
            var b = 1;
      }
      a // ReferenceError: a is not defined.
      b // 1
    

    严格模式主要有以下限制。

    • 变量必须声明后再使用
    • 函数的参数不能有同名属性,否则报错
    • 不能使用with语句
    • 不能对只读属性赋值,否则报错
    • 不能使用前缀0表示八进制数,否则报错
    • 不能删除不可删除的属性,否则报错
    • 不能删除变量delete prop,会报错,只能删除属性delete global[prop]
    • eval不会在它的外层作用域引入变量
    • eval和arguments不能被重新赋值
    • arguments不会自动反映函数参数的变化
    • 不能使用arguments.callee
    • 不能使用arguments.caller
    • 禁止this指向全局对象
    • 不能使用fn.caller和fn.arguments获取函数调用的堆栈
    • 增加了保留字(比如protected、static和interface)

    不存在变量提升

    var命令会发生变量提升现象,即变量可以在声明之前使用,值为undefined。这种现象多多少少是有些奇怪的,按照一般的逻辑,变量应该在声明语句之后才可以使用。

    为了纠正这种现象,let命令改变了语法行为,它所声明的变量一定要在声明后使用,否则报错

        console.log(foo); // 输出undefined
        var foo = 2;
    
        // let 的情况
        console.log(bar); // 报错ReferenceError
        let bar = 2;
    
    暂时性死区

    ES6明确规定,如果区块中存在 letconst命令,这个区块对这些命令声明的变量,从一开始就形成了封闭作用域。凡是在声明之前就使用这些变量,就会报错。

        var tmp = 0;
        if(true) {
            tmp = 2;
            let tmp;
        }
    
    Paste_Image.png
        tmp = 0;
    
    Paste_Image.png

    在没有let之前,typeof运算符是百分之百安全的,永远不会报错。现在这一点不成立了。这样的设计是为了让大家养成良好的编程习惯,变量一定要在声明之后使用,否则就报错。

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

    不允许重复声明

    let不允许在相同作用域内,重复声明同一个变量。

    class的基本用法

    在ES5中,大部分是通过构造函数的形式来定义类。
    在es6中,新添加一种通过class构造类的方法。

        class Point {
          constructor(x, y) {
              this.x = x;
              this.y = y;
            }
    
          toString() {
            return '(' + this.x + ', ' + this.y + ')';
            }
        }
    

    在上述定义中,constructor就是构造方法,相当与构造函数,this就是构造的实例对象
    Point类除了构造方法,还定义了一个toString方法。

    注意,定义“类”的方法的时候,前面不需要加上function这个关键字,直接把函数定义放进去了就可以了。
    另外,方法之间不需要逗号分隔,加了会报错。

    使用的时候,也是直接对类使用new命令,跟构造函数的用法完全一致。

    class Point {
        constructor(props) {
            this.x = x;
            this.y = y;
        }
    
        toString() {
            return '(' +this.x + ', ' + this.y + ')';
        }
    }
    
    var m = new Point(x = 2, y = 4);
    console.log(m.toString());
      //(2, 4)
    
    在类的实例上面调用方法,其实就是调用原型上的方法。
      class B {}
      let b = new B();
      b.constructor === B.prototype.constructor
    

    在这个例子中,bB的实例,B.prototype是原型,
    由于类的方法都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法

           class Point {
        constructor(x, y) {
          this.x = x;
          this.y = y;
        }
      }
      Object.assign(Point.prototype, {
        toValue() {},
        toString() {}
      });
    

    prototype对象的constructor属性,直接指向“类”的本身

      console.log(Point === Point.prototype.constructor);  //true
    
    类的内部所有定义的方法,都是不可枚举的(non-enumerable)
    class Point {
        constructor(x, y) {
          this.x = x;
          this.y = y;
        }
    
         toString() {}         
      }
    

    constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加。

     class Point {
        constructor(x, y) {
          return Object.create(null);
        }
    
         toString() {}
    
      }
      
      var m = new Point();
      console.log(m instanceof Point);
    

    该例子显示定义了constructor返回的创建一个空对象,所以输出为false

    类的构造函数,不使用new是没法调用的,会报错

    class Point {
        constructor(x, y) {
          return Object.create(null);
        }
    
         toString() {}
    
      }
      
      var m = Point();
    
    Paste_Image.png

    与ES5一样,实例的属性除非显式定义在其本身(即定义在this对象上),否则都是定义在原型上,就是说实例输入的值是其本身属性,其他均为原型属性

      class Point {
        constructor(x, y) {
          this.x = x;
          this.y = y;
        }
    
         toString() {
          return '(' + this.x + ',' + this.y + ')';
         }
    
      }
      
      var m = new Point(1,2);
      console.log(m.hasOwnProperty('x'));    //true
      console.log(m.hasOwnProperty('y'));    //true
      console.log(m.hasOwnProperty('toString'));      //false
      console.log(m.__proto__.hasOwnProperty('toString'));  //true,记住了原型是这样的
    

    可以通过实例的__proto__属性为Class添加方法

    var m = new Point(1,2);
      var n = new Point(2,8)
    
      m.__proto__.playTime = function () {
        return this.x + this.y + ' minutes';
      }
      console.log(m.playTime()); 
      console.log(n.playTime());
    
    Paste_Image.png
    这意味着,使用实例的proto属性改写原型,必须相当谨慎,不推荐使用,因为这会改变Class的原始定义,影响到所有实例。

    Class不存在变量提升(hoist),这一点与ES5完全不同。要先定义后使用,否则报错。
    因为ES6不会把类的声明提升到代码头部。

        {
            let Foo = class {};
            class Bar extends Foo {
                }
        }
    

    上面的代码不会报错,因为Bar继承Foo的时候,Foo已经有定义了。但是,如果存在class的提升,上面代码就会报错,因为class会被提升到代码头部,而let命令是不提升的,所以导致Bar继承Foo的时候,Foo还没有定义。

    Class表达式

    类的名字是MyClass,Me只在内部有定义,所以Me是可以省略的

    const MyClass = class Me {       
        getClassName() {
          return Me.name;
        }
      };
    
      let inst = new MyClass();
      console.log(inst.getClassName());   
      console.log(Me.name);
    
    Paste_Image.png

    this的指向

    类的方法内部如果含有this,它默认指向类的实例。但是,必须非常小心,一旦单独使用该方法,很可能报错。
    一个比较简单的解决方法是,在构造方法中绑定this,这样就不会找不到print方法了

    class Logger {
        constructor() {
        this.printName = this.printName.bind(this);
          }
    
          // ...
      }
    

    另一种解决方法是使用箭头函数

    class的继承

    Class之间可以通过extends关键字实现继承

        class ColorPoint extends Point {}
    
        class ColorPoint extends Point {
                constructor(x, y, color) {
                super(x, y); // 调用父类的constructor(x, y)
                this.color = color;
          }
    
        toString() {
            return this.color + ' ' + super.toString(); // 调用父类的toString()
          }
      }
    

    super关键字,它在这里表示父类的构造函数,用来新建父类的this对象。

    子类必须constructor方法中调用super方法,否则新建实例时会报错。这是因为子类没有自己的this对象,而是继承父类的this对象,然后对其进行加工。如果不调用super方法,子类就得不到this对象。

    如果子类没有定义constructor方法,这个方法会被默认添加,代码如下。也就是说,不管有没有显式定义,任何一个子类都有constructor方法。

        constructor(...args) {
                super(...args);
            }
    

    另一个需要注意的地方是,在子类的构造函数中,只有调用 super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,是基于对父类实例加工,只有super方法才能返回父类实例。

        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; // 正确
            }
        }
    

    super

    super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。

    • 第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。

          class A {}
      
          class B extends A {
                constructor() {
                super();
            }
        }
      

    上面代码中,子类B的构造函数之中的super(),代表调用父类的构造函数。这是必须的,否则 JavaScript 引擎会报错。

    • 第二种情况,super作为对象时,指向父类的原型对象
      这里需要注意,由于super指向父类的原型对象,所以定义在父类实例上的方法或属性,是无法通过super调用的
      不过,如果属性定义在父类的原型对象上,super就可以取到

    相关文章

      网友评论

          本文标题:ES6几点知识

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