ES6 - Class

169 阅读5分钟

Class

  • 内部存在constructor成为构造函数,方法内部的this指向实例,默认的构造函数为一个空函数,每次new的时候都会执行该函数。
  • 类的类型是函数,类本身就指向构造函数。
  • 类内部所有定义的方法都是不可枚举的。
  • 类必须使用new实例化,否则会报错。
  • 类的属性和方法除非显式定义在本身(this)上,否则全部定义在原型(class)上。
  • ES2022 类的属性除了定义在constructor中的this上,还可以直接定义在类的最顶层,这种定义的属性是定义在实例对象自身的属性,而不是定义在实例对象原型上的方法。
  • 类内部定义的属性也可以定义存值(set)和取值(get)函数,对属性的访问和赋值进行自定义。
  • 类的属性名可以采用表达式的方式。
  • 类也可以使用表达式的形式定义const MyClass = class Me{ ... },此时类的名字是Me但是这个名字只能在类的内部调用,若内部没有用到,则可以省略,直接定义const MyClass = class { ... }。外部只能使用MyClass。这种方式定义类还可以定义立即执行Classconst person = { ... }(params)
  • 在类上定义的属性和方法都会被实例继承,但是在属性或者方法前加上static,表示属于静态方法或属性,只能通过来来调用,实例不会继承这些。如果静态方法包含this,这个this指向的是类本身,而不是实例对象。
  • 父类的静态方法可以被子类继承,静态方法可以在super上被调用。静态属性相同。
  • 类内部的私有属性可以在属性或方法前加 # 表示,外部访问会报错,即使访问不存在的私有属性也会报错。只有内部或者实例可以使用私有属性或方法。
  • 静态块在类生成的时候运行一次,主要是用作对静态属性的初始化,静态块的内部可以使用类名或者this,指向当前类,另一个作用是将私有属性与类的外部代码分享。
    let getX;
    
    export class C {
      #x = 1;
      static {
        getX = obj => obj.#x;
      }
    }
    console.log(getX(new C())); // 1
    

###注意点

  • 类不存在变量提升
  • name属性总是返回紧跟在class关键字后面的类名
  • this指向问题:类的方法内部的this默认指向类的实例,但是如果将该方法单提出来使用可能会报错,有两种结婚方法:
class Logger {
  constructor() {
    this.printName = this.printName.bind(this);
  }

  // ...
}
class Obj {
  constructor() {
    this.getThis = () => this;
  }
}

const myObj = new Obj();
myObj.getThis() === myObj // true

还有一种就是proxy代理,在获取时自动绑定this,实现比较麻烦。

  • new.target 返回new命令作用的类的构造函数,如果构造函数不是通过new调用会返回undefined,该属性可以用来确定构造函数是怎么调用的。
  • class内部调用new.target,会返回当前的Class。
  • 子类继承父类时,new.target会返回子类。利用这一点可以写出不能独立使用,只能被继承的类。

类的继承

  • 子类必须在构造函数内调用super方法,用于新建一个父类的实例对象。原因是子类自己的this对象必须先通过父类的构造函数完成塑造,得到与父类同样的实例方法和属性,再添加子类自己的属性或方法,如果不调用子类就找不到自己的this对象。
  • 新建子类实例时,父类的构造函数必定会先运行一次。
  • 在子类的构造函数中,只有调用的super之后,才能使用this关键字,否则会报错。因为子类实例的构建,必须先完成父类的继承。
  • 除了私有属性,父类的所有属性和方法都会被子类继承,包括静态方法。
  • 子类无法继承父类的私有属性,私有属性只能在定义它的class中使用。
  • 如果父类定义了私有属性的读写方法,子类可以通过这些方法,读写私有属性。
  • Object.getPrototypeOf()方法可以用来从子类上获取父类。可以用于判断一个类是否继承了另一个类。

super

  • 子类构造函数中的super()表示调用父类的构造函数,且是必须调用的,但是返回的是子类的实例。super()的调用只能是在子类的构造函数内部,在其他地方调用会报错。
    class A {
      constructor() {
        console.log(new.target.name);
      }
    }
    class B extends A {
      constructor() {
        super();
      }
    }
    new A() // A
    new B() // B
    
  • super作为对象时,在普通方法中调用,指向父类的原型对象,在静态方法中指向父类。
  • 在普通方法中调用时指向父类的原型对象,所以父类实例上的方法或者属性是无法通过super访问的。如果定义在父类的原型上则可以访问。
  • 在子类普通方法中的通过super调用父类的方法是,方法内部的this指向是当前子类的实例。
  • 由于this指向子类实例,所以如果通过super对某个属性赋值,这时super就是this,赋值的属性会变成子类实例的属性。
  • 在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类的实例。
    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
    
  • 由于对象总是继承其他对象的,所以可以在任意一个对象中,使用super关键字。
    var obj = {
      toString() {
        return "MyObject: " + super.toString();
      }
    };
    
    obj.toString(); // MyObject: [object Object]
    

proto prototype

  • 作为一个对象,子类(B)的原型(__proto__属性)是父类(A);作为一个构造函数,子类(B)的原型对象(prototype属性)是父类的原型对象(prototype属性)的实例
  • class可以实现源生构造函数的继承。