ECMAScript6 从入门到入坟,你敢来挑战吗???(四)

简介: ECMAScript6 从入门到入坟,你敢来挑战吗???(四)

new.target 属性

new 是从构造函数生成实例对象的命令,ES6 为 new 命令引入一个 new.target 属性,该属性一版用在构造函数中,返回 new 命令作用域的那个构造函数。如果构造函数不是通过 new 或者 Reflect.construct
调用,
new.target 会返回 undefined,因此这个属性可以用来确定构造函数时怎么调用的。

function Person(name) {
      if (new.target !== undefined) {
        this.name = name;
      } else {
        throw new Error('必须使用 new 命令生成实例');
      }
    }
    // 另一种写法
    function Person(name) {
      if (new.target === Person) {
        this.name = name;
      } else {
        throw new Error('必须使用 new 命令生成实例');
      }
    }
    var person = new Person('张三'); // 正确
    var notAPerson = Person.call(person, '张三');  // 报错

Class 内部调用 new.target ,返回当前 Class

class Rectangle {
      constructor(length, width) {
        console.log(new.target === Rectangle);
        this.length = length;
        this.width = width;
      }
    }
    var obj = new Rectangle(3, 4); // 输出 true

当子类集成父类时,new.target 会返回子类。

class Rectangle {
      constructor(length, width) {
        console.log(new.target === Rectangle);
        // ...
      }
    }
    class Square extends Rectangle {
      constructor(length, width) {
        super(length, width);
      }
    }
    var obj = new Square(3); // 输出 false

利用这个特点,可以写出不能独立使用、必须继承后才能使用的类。

class Shape {
      constructor() {
        if (new.target === Shape) {
          throw new Error('本类不能实例化');
        }
      }
    }
    class Rectangle extends Shape {
      constructor(length, width) {
        super();
        // ...
      }
    }
    var x = new Shape();  // 报错
    var y = new Rectangle(3, 4);  // 正确

1.class 的继承

简介

class 可通过 extends 关键字来实现继承,这比ES5的通过修改原型链实现继承方便很多

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

上面代码中,constructor 方法和 toString 方法之中,都出现了 super 关键字,在这里表示父类的构造函
数,用来新建父类的 this 对象。

子类必须在 constructor 方法中调用 super 方法,否则新建实例时就会报错,这是因为子类自己的 this
对象, 必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后对其进行加工,加上子类自己的实例属性和方法。如果不调用
super 方法,子类就得不到 this 对象。

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

  // 这段代码中,因为在子类集成父类的时候没有调用super() 方法,在新建实例时出现报错。

ES5 继承,实质上是先创造子类的实例对象的 this 对象,然后再将父类的方法添加到 this 上面,
Parent.apply(this)

ES6 继承机制完全不同,实质是将父类的实例对象的属性和方法,加到 this 上面(所以必需先调用 super 方法),
然后用子类的构造函数修改 this

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

class ColorPoint extends Point {
    }
    // 等同于
    class ColorPoint extends Point {
      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; // 正确
      }
    }

父类的静态方法,也会被子类继承

class A {
      static hello() {
        console.log('hello world');
      }
    }
    class B extends A {
    }
    B.hello()  // hello world

Object.getPrototypeOf()

Object.getPrototypeOf() 方法可以从子类上获取父类。

   Object.getPrototypeOf(ColorPoint) ===Point
   // true

使用这个方法,可以判断一个类是否继承了另一个类

super 关键字

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

1.用法一:super 关键字作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数
必需执行一次 super 函数。

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


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

注意:这里在子类当中调用 super() 虽然代表了父类 A 的构造函数,但是返回的是子类 B 的实例,即
super 内部的 this 指的是 B 的实例,因此 super 在这里相当于
A.prototype.constructor.call(this)

super 作为函数时,super() 只能用在子类的构造函数中,用在其他地方就会报错。

class A {}
    class B extends A {
      m() {
        super(); // 报错
      }
    }


   // 上面代码中,super()用在B类的m方法之中,就会造成语法错误。

1.用法二:super 作为对象时,在普通方法中,指向的父类的原型对象,在静态方法中,指向父类。

class A {
      p() {
        return 2;
      }
    }
    class B extends A {
      constructor() {
        super();
        console.log(super.p()); // 2
      }
    }
    let b = new B();
    /**

       上面代码中,子类B 当中的 super.p()就是将super 当做一个对象使用,这是super在普通方法中,指向
       A.prototype,所以super.p()就相当于A.prototype.p()。
   */

注意:由于 super 指向父类的原型对象,所有定义在父类实例上的方法或者属性,是无法通过 super 调用的。

class A {
      constructor() {
        this.p = 2;
      }
    }
    class B extends A {
      get m() {
        return super.p;
      }
    }
    let b = new B();
    b.m // undefined

  // 由于 A 中的p 是定义在A 类的构造函数上的,所以无法通过super获取到p

上面代码中,super 当对象使用时,无法访问到父类构造函数中属性和方法,因为是定义在 A 类的构造
函数上,如果定义在 p 定义在 A.prototype 上,可以通过 super.p 访问到。

ES6 规定,在子类普通方法中通过 super 调用父类的方法时,方法内部的 this 指向当前的子类实例。

class A {
      constructor() {
        this.x = 1;
      }
      print() {
        console.log(this.x);
      }
    }
    class B extends A {
      constructor() {
        super();
        this.x = 2;
      }
      m() {
        super.print();
      }
    }
    let b = new B();
    b.m() // 2
    /**

       上面代码中,`super.print()` 虽然调用的是 `A.prototype.print()`,但是 `A.prototype.print()` 内部
   的 `this` 指向子类的 B 的实例,导致输出的是 2,而不是 1,也就是说,实际上执行的是

`super.print.call(this)`。
    */
    class A {
      constructor() {
        this.x = 1;
      }
    }
    class B extends A {
      constructor() {
        super();
        this.x = 2;
        super.x = 3;
        console.log(super.x); // undefined
        console.log(this.x); // 3
      }
    }
    let b = new B();
    /**

       上面代码中,super.x = 3,这等同于对 this.x = 3,而当读取 super.x时,读的是 A.prototype.x,
       返回的时 undefined
   */

如果 super 作为对象,用在静态方法中,这时 super 将指向父类,而不是父类的原型对象。

class Parent {
      static myMethod(msg) {
        console.log('static', msg);
      }
      myMethod(msg) {
        console.log('instance', msg);
      }
    }
    class Child extends Parent {
      static myMethod(msg) {
        super.myMethod(msg);
      }
      myMethod(msg) {
        super.myMethod(msg);
      }
    }
    Child.myMethod(1); // static 1
    var child = new Child();
    child.myMethod(2); // instance 2

上面代码中,super 在静态方法中指向父类,在普通方法中指向父类的原型对象。

class A {
      constructor() {
        this.x = 1;
      }
      static print() {
        console.log(this.x);
      }
    }
    class B extends A {
      constructor() {
        super();
        this.x = 2;
      }
      static m() {
        super.print();
      }
    }
    B.x = 3;
    B.m() // 3
    /**

       上面代码中,静态方法B.m里面,super.print指向父类的静态方法,这个方法里面的this指向的是B,而不是
       B的实例。
   */

注意,在使用 super 时,必须显示的执行是作为函数还是作为对象,否则都会报错。

class A {}
    class B extends A {
      constructor() {
        super();
        console.log(super); // 报错
      }
    }

由于对象总是继承其他对象的,所以可以在任意一个对象中,使用 super 关键字。

var obj = {
      toString() {
        return "MyObject: " + super.toString();
      }
    };
    obj.toString(); // MyObject: [object Object]
相关文章
|
4月前
|
算法 C++
惊爆!KPM算法背后的秘密武器:一行代码揭秘字符串最小周期的终极奥义,让你秒变编程界周期大师!
【8月更文挑战第4天】字符串最小周期问题旨在找出字符串中最短重复子串的长度。KPM(实为KMP,Knuth-Morris-Pratt)算法,虽主要用于字符串匹配,但其生成的前缀函数(next数组)也可用于求解最小周期。核心思想是构建LPS数组,记录模式串中每个位置的最长相等前后缀长度。对于长度为n的字符串S,其最小周期T可通过公式ans = n - LPS[n-1]求得。通过分析周期字符串的特性,可证明该方法的有效性。提供的C++示例代码展示了如何计算给定字符串的最小周期,体现了KPM算法在解决此类问题上的高效性。
90 0
|
JSON 前端开发 JavaScript
ECMAScript6 从入门到入坟,你敢来挑战吗???(一)
ECMAScript6 从入门到入坟,你敢来挑战吗???
|
前端开发 JavaScript
ECMAScript6 从入门到入坟,你敢来挑战吗???(二)
ECMAScript6 从入门到入坟,你敢来挑战吗???(二)
|
缓存 JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(五)
ECMAScript6 从入门到入坟,你敢来挑战吗???(五)
|
缓存 JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(六)
ECMAScript6 从入门到入坟,你敢来挑战吗???(六)
|
JavaScript 前端开发
ECMAScript6 从入门到入坟,你敢来挑战吗???(三)
ECMAScript6 从入门到入坟,你敢来挑战吗???(三)
程序媛才能读懂的高级情话
程序媛才能读懂的高级情话
174 0
|
前端开发 JavaScript
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
98 0
#yyds干货盘点# 前端歌谣的刷题之路-第二十三题-检测复杂数据类型
|
前端开发
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(二)🔥
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(二)🔥
|
存储 前端开发
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(三)🔥
草系前端手摸手带你实现正则引擎,点燃夏日最热情的烟火(三)🔥
112 0
下一篇
DataWorks