学习 class 中的集成

41 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第 27 天,点击查看活动详情

学习 class 中的集成

start

  • 前面学习了 class 的基础使用。点击这里
  • 今天继续学习一下 class 实现的继承。

基础知识

什么是类

类是指用来创造一类对象的模板,而通过这个模板创建出来的对象叫做实例。

什么是继承

继承是一种类(class)与类之间的关系,JS 中没有类,但是可以通过构造函数模拟类,然后通过原型来实现继承,

  • 继承是为了实现数据共享,js 中的继承当然也是为了实现数据共享。
  • 继承是子类继承父类的特征或者行为,使子类也具有父类的属性和方法;
  • 或者子类从父类继承方法,使得子类具有父类相同的行为
  • 继承可以使得子类具有父类的各种属性和方法,而不需要再次编写相同的代码。

ES5 中使用最广泛的 组合继承

function Person(name) {
  this.name = name
  this.colors = ['red', 'blue', 'green']
}
Person.prototype.sayName = function () {
  console.log(this.name)
}
function Son(name, age) {
  Person.call(this, name)
  this.age = age
}

var s1 = new Son('大头儿子', 8)

console.log(s1)
// Son { name: '大头儿子', colors: [ 'red', 'blue', 'green' ], age: 8 }

梳理一下上述代码的逻辑

  1. 首先定义了一个构造函数 Person
  2. 然后在 Person 的原型上添加方法;
  3. 定义一个子类, 构造函数 Son;
  4. new Son('大头儿子', 8) 的过程中:
    • new 创建了一个空对象 s1 = {}
    • 对象 s1 的隐式原型指向函数的显式原型(Son.prototype)
    • 函数中的 this 指向这个对象 s1 && 运行函数

      运行函数的过程中执行了Person.call(this, name),所以会给s1.name,s1.colors赋值。

    • new Son() 返回结果 ,Son 函数有返回对象,输出这个对象; Son 函数没有返回对象,输出 new 出来的对象 s1
    • 输出对象 s1
  5. 打印的s1中就包含 name,colors两个属性;

class 中的继承

Class 可以通过 extends 关键字实现继承,让子类继承父类的属性和方法。

1. 基础使用

演示代码:

class Person {}

class Son extends Person {
  constructor(x, y, color) {
    super(x, y) // 调用父类的constructor(x, y)
    this.color = color
  }

  toString() {
    return this.color + ' ' + super.toString() // 调用父类的toString()
  }
}

注意事项:

  • extends 英文释义:继承。

  • super 在这里表示父类的构造函数,用来新建一个父类的实例对象。

  • ES6 规定,子类必须在 constructor() 方法中调用 super(),否则就会报错。

    • 子类自己的 this 对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,添加子类自己的实例属性和方法。如果不调用 super()方法,子类就得不到自己的 this 对象。

    • 所以如果,在 super 之前使用 this 也会报错:

      class Person {}
      class Son extends Person {
        constructor(x, y, color) {
          this.color = color
          super(x, y) // 调用父类的 constructor(x, y)
        }
      }
      new Son(1, 2, 3)
      // ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor
      
  • 对比 ES5 的继承

    • ES5 的继承机制,是先创造一个独立的子类的实例对象,然后再将父类的方法添加到这个对象上面;
    • ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例;

2. 类的静态属性会被继承

演示代码:

class Person {
  static boo = 123
  static foo = { num: 10 }
}
class Son extends Person {}

console.log(Son.boo) // 123
console.log(Son.foo) // { num: 10 }

注意事项:

  1. 静态属性其实就是Person自身的属性,例如:Person.boo = 123

  2. 注意静态属性是继承到子类上,不是子类的实例上。

  3. 继承的方式是浅拷贝。

    class Person {
      static boo = 123
      static foo = { num: 10 }
    }
    class Son extends Person {}
    
    class Jemi extends Person {}
    
    console.log(Son.boo) // 123
    console.log(Son.foo) // { num: 10 }
    console.log(Jemi.boo) // 123
    console.log(Jemi.foo) // { num: 10 }
    
    Son.boo++
    Son.foo.num++
    
    console.log(Son.boo) // 124
    console.log(Son.foo) // { num: 11 }
    console.log(Jemi.boo) // 123
    console.log(Jemi.foo) // { num: 11 }
    

3. 获取子类的父类

演示代码:

class Person {}
class Son extends Person {}

console.log(Object.getPrototypeOf(Son)) // [Function: Person]
console.log(Object.getPrototypeOf(Son) === Person) // true

4. super 的使用

第一种情况,super 作为函数调用时:

代表父类的构造函数。

演示代码:

class A {}

class B extends A {
  constructor() {
    super()
  }
}
// super虽然代表了父类A的构造函数,但是返回的是子类B的实例

注意事项:

super以函数的形式调用:主要就是在子类的constructor()调用。

尝试了其他几种方式使用,都会报错:

  • 父类中使用super()
  • 子类的静态属性中使用super()
  • 子类原型上的属性中使用super()

第二种情况,super 作为对象时:

在普通方法中,指向父类的原型对象,this 指向当前的子类实例;在静态方法中,指向父类,this 指向当前的子类。

简单来说就是 super.xxx的形式。

演示代码:

class A {
  constructor() {}
  static foo() {
    console.log('A父类自身的foo方法', this.a)
  }

  foo() {
    console.log('A父类的原型上的foo方法', this.a)
  }
}

class B extends A {
  static a = 111
  a = 222

  constructor() {
    super()
  }

  static say() {
    super.foo()
  }

  say() {
    super.foo()
  }
}

var b1 = new B()

/* 1 普通方法中的super以对象的形式使用,指向父类的原型,this指向子类的实例。*/
b1.say() // A父类的原型上的foo方法 222

/* 2 静态方法中的super以对象的形式使用,指向父类的原型,this指向子类的实例。*/
B.say() // A父类自身的foo方法 111

解释上述案例:

  1. 基础知识储备

    • constructor(x, y) {} 等同于 ES5 中的 function A(x,y){}

    • static foo 等同于 ES5 中的 A.foo

    • foo 等同于 ES5 中的 A.prototype.foo

  2. 注意这里的 super 是采取对象的形式使用。

  3. b1.say()

    • b1自身中没有say
    • 沿着原型链,找到 B.prototype 上有 say
    • super.foo(),普通方法的super指向父类原型,也就是A.prototype
    • 所以打印:A父类的原型上的foo方法
    • 后续的this,谁调用就指向谁,所以this指向 b1b1.a 222
  4. B.say()

    • B.say,调用的是B 中的 static say
    • super.foo(),静态方法的super指向父类,也就是A
    • super.foo(),静态方法的super指向父类,也就是A
    • 所以打印:A父类自身的foo方法
    • 后续的this,谁调用就指向谁,所以this指向 BB.a 111

end

  • 完结。