JS笔记《class的继承》

125 阅读4分钟

概述

  • class可以通过extends实现继承,让子类继承父类的属性和方法。
  • ES6规定,子类必须在constructor方法中调用super,否则就报错。因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再添加子类自己的实例属性和方法。也就是说,新建子类实例时,父类的构造函数必定会先运行一次。
// 
class Person{
  constructor(name, age){
    this.name = name;
    this.age = age;
  }

  sayName(){
    console.log('my name is' + this.name);
  }
}

class Student extends Person{
  constructor(name, age, grade){
    super(name, age);  // 调用父类的constructor
    this.grade = grade;
  }

  sayName(){
    super.sayName();
    console.log('i am ' + this.grade);
  }
}

let s = new Student('张三', 18, '三年级');
console.log(s)  // Student {name: '张三', age: 18, grade: '三年级'}
s.sayName()     // my name is张三 
                // i am 三年级
  • 在子类的构造函数中,只有调用super之后,才可以使用this关键字,否则会报错。这是因为子类实例的构建,必须先完成父类的继承,只有super()方法才能让子类实例继承父类。
  • 如果子类没有定义constructor方法,这个方法会默认添加,并且里面会调用super()
  • Object.getPrototypeOf()方法可以用来判断一个类是否继承了另一个类。
class Point { }

class ColorPoint extends Point { }

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

私有属性、方法的继承

  • 父类所有的属性和方法都会被子类继承,除了私有的属性和方法。
class Foo {
  #p = 1; // 私有属性
  #m() {  // 私有方法
    console.log('hello');
  }
}

class Bar extends Foo {
  constructor() {
    super();
    console.log(this.#p);   // 报错
    this.#m();    // 报错
  }
}
  • 只有父类定义了私有属性的读写方法,子类才可以通过这些方法读写私有属性。
class Foo {
  #p = 1;
  getP() {
    return this.#p;
  }
}

class Bar extends Foo {
  constructor() {
    super();
    console.log(this.getP()); // 1
  }
}

静态属性、方法的继承

  • 父类的静态属性和静态方法,也会被子类继承。
class A {
  static hello() {
    console.log('hello world');
  }
}

class B extends A {}

B.hello()  // hello world
  • 静态属性是通过浅拷贝实现继承的,如果父类的静态属性的值是一个对象,那么子类的静态属性也会指向这个对象。
class A {
  static foo = { n: 100 };
}

class B extends A {
  constructor() {
    super();
    B.foo.n--;
  }
}

const b = new B();
B.foo.n // 99  
A.foo.n // 99  指向同一个地址

super

  • super关键字既可以当作函数使用,又可以当作对象使用。

作为函数

  • 作为函数调用时,只能用在子类的构造函数中,用在其他地方会报错,此时super代表的是父类的构造函数constructor
class A {
  constructor() {
    console.log(new.target.name);
  }
}

class B extends A {
  constructor() {
    super();  // 相当于调用 A 的 constructor()
    // 虽然super代表了父类的构造函数,但是因为返回的是子类的this,
    // 所以super内部的this指向的是子类的实例
  }
}

new A() // A
new B() // B new.target指向当前正在执行的函数,返回 B 表示 A的constructor是B在调用,this指向的是B
  • 由于super在子类构造函数中执行时,子类的属性和方法还没有绑定到this,所以如果存在同名属性,此时拿到的是父类的属性。
class A {
  name = 'A';
  constructor() {
    console.log('My name is ' + this.name);
  }
}

class B extends A {
 // 子类没有constructor,相当于默认添加一个,并调用super()
  name = 'B';
}

const b = new B(); // My name is A

作为对象

普通方法

  • super作为对象时,在普通方法中指向父类的原型对象,所以定义在父类实例上的方法或属性是无法通过super调用的,但是定义在父类原型对象上的属性或方法却可以调用。
class A {
  p() {  // 父类中定义的方法默认相当于在原型对象上定义
    return 2;
  }
}

class B extends A {
  constructor() {
    super();
    console.log(super.p());  // 2
  }
}

let b = new B();
  • 在子类普通方法中通过super调用父类的方法时,方法内部的this指向子类实例。
class A {
  constructor() {
    this.x = 1;
  }
  print() {
    console.log(this.x);  // this 指向的是 b 
  }
}

class B extends A {
  constructor() {
    super();
    this.x = 2;
  }
  m() {
    super.print();  // 调用父类的print方法
  }
}

let b = new B();
b.m()  // 2

静态方法

  • 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);  // 指向父类,相当于执行 Parent.myMethod
  }

  myMethod(msg) {
    super.myMethod(msg);  // 指向父类的原型对象,相当于执行Parnet.prototype.myMethod
  }
}

Child.myMethod(1); // static 1  

var child = new Child();
child.myMethod(2); // instance 2
  • 在子类的静态方法中通过super调用父类的方法时,方法内部的this指向当前的子类,而不是子类实例。
class A {
  static x = 1;
  static print() {
    console.log(this.x);  // this指向子类,等同于 B.x
  }
}

class B extends A {
  static x = 2;
  static m() {
    super.print(); // 调用 A.print
  }
}

B.m() // 2