和之前讲的js的继承方式不同,class的继承是es6里面才有的。在讲class的继承之前,需要先了解一下class:

class的基本语法

class,也就是我们常说的类,

// class
class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  sayName() {
    console.log(this.name)
  }
}
const p1 = new Person('p1', 18)

// 组合使用构造函数模式和原型模式
function Animal(name, age) {
  this.name = name
  this.age = age
}
Animal.prototype.sayName = function() {
  console.log(this.name)
}
const a1 = new Animal('a1', 18)

console.log(p1, a1)

在这里插入图片描述
可以看到,通过class生成的实例和通过组合使用构造函数模式和原型模式所生成的实例是一样的。

  • 默认存在constructor方法,该方法默认返回实例对象(即this,也就是说constructor里面的this指向类的实例)。
    在这里插入图片描述
  • 类的所有方法都定义在类的prototype上
  • 必须通过new来调用class
  • 类里面的方法不需要用逗号隔开,且是不可枚举的
  • 类默认使用严格模式,且不存在提升机制,也就是说在类定义之前使用它是不行的
  • get和set关键字可对属性的存取行为进行拦截(该属性不用显示地定义)
class Person {
 constructor(name, age) {
   this.name = name
   this.age = age
 }
 get Sex() {
   console.log('sex')
   return 'sex'
 }
 set Sex(val) {
   console.log(this.name + this.age + val)
 }
}
const p1 = new Person('boy', 18)
p1.Sex = '南'
console.log(p1)

在这里插入图片描述

  • 静态方法。静态方法指的是通过类直接可以调用并且不被实例所继承的方法。定义静态方法的步骤就是直接在方法前面加是static。
class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  static sayName() {
    console.log('静态方法')
  }
}
const p1 = new Person('boy', 18)
p1.Sex = '南'
console.log(p1)

在这里插入图片描述

  • 静态属性(ES6 明确规定,Class 内部只有静态方法,没有静态属性),静态属性是指class本身的属性,在声明类之后再添加静态属性,如:
    在这里插入图片描述
    虽然规定了内部没有静态属性,不过也出现了这方面的提案,就是在类的内部通过static关键字定义静态属性,如:
    在这里插入图片描述
    class就先了解到这里,其他的知识点不展开,进入主题。
class的继承

class的继承围绕三点进行展开描述:如何实现继承,super,多重继承。

1、如何实现继承

class通过关键字extends实现继承。具体操作步骤如下:

class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }
  sayName() {
    console.log('Person')
  }
}

class Animal extends Person {
  
}
const a = new Animal()
a.sayName() // Person

需要注意的点:

  • 子类必须在constructor方法中调用super方法
    上面提到过,constructor会默认添加,这里super也会默认添加,所以上面的例子会正常执行。
    为什么必须要调用super方法呢?
    这是因为这里的继承是先塑造父类,再塑造子类。也就是先将父类的东西都拿过来之后,再进行子类的添加。而super负责的就是将父类的东西拿过来。所以必须先调用super。
  • 需要在调用super之后才能使用this。也就是说在子类构造函数constructor里面,super函数要放在最上面。为什么要这样呢?
    刚才提到过,class的继承是先将父类拿过来再进行子类的添加,之前在讲借用构造函数实现继承的时候也提到过,如果想要给自身添加属性和方法,需要在调用call或者apply之后添加。否则如果存在同名属性,后面的会覆盖前面的。这里其实也是同样的道理。
2、super

class里面的super有两种使用方式,一种是当方法用,一种是当对象用。

  • 当方法用
    当方法用就像上面提到的,在constructor里面调用,负责将父类的实例属性拿过来。这个时候,super里面的this指向的是子类。为什么这样呢?其实和借用构造函数实现继承是一样的,子类继承超类的实例属性时,就是通过call或者apply在子类调用超类构造函数。这个时候,this自然指向的就是子类。还要注意一点 的是,其实上面也提到了,当方法用的时候,super必须放到constructor构造函数的最上层
  • 当对象用
    上一点说了,当方法用是为了将父类的实例属性继承过来(其实就是复制一份过来)。实例属性是拿到了,但方法呢,比如说有时候子类的方法想要调用父类的某个方法,这个时候要如何做?
class Person {
   constructor(name, age) {
     this.name = name
     this.age = age
   }
   sayName() {
     console.log('Person')
   }
   static speak() {
     console.log('speak')
   }
 }
 class Animal extends Person {
   sayName() {
     super.sayName() // Person
   }
   static speak() {
     super.speak() // speak
   }
 }
 const a = new Animal()
 a.sayName()
 Animal.speak()

这个时候,super作为对象的所用就体现出来了,通过它可以调用父类的方法(包括静态方法)。
注意,不同类型的方法要在不同类型的方法里面调用。有点绕,其实就是子类的静态方法里面通过super只能调用父类的静态方法,不能调用父类的原型方法。同理,子类的原型方法通过super也只能调用父类的原型方法,不能调用父类的静态方法。
有点像组合继承,super方法就是借用构造函数继承,继承实例属性,super对象就是原型链继承,继承原型上的属性和方法。也就是说super对象是无法访问父类的实例属性的
那么还有一个问题,通过super调用父类的方法,那么该方法内的this指向的是谁呢?

class Person {
  constructor() {
    this.name = 'Person'
  }
  sayName() {
    console.log(this.name)
  }
}
class Animal extends Person {
  constructor() {
    super()
    this.name = 'Animal'
  }
  sayName() {
    super.sayName() // Person
  }
}

在这里插入图片描述
结果就是,this指向的是当前类(子类)的实例。所以我们通过super对某个属性进行修改,修改的就是子类实例的属性。

3、多重继承

有些面向对象编程语言是支持多重继承(即一个子类继承多个父类),如c++, py。java不支持多继承,但可以通过实现多接口或者内部类的方式实现类似的效果。es6的class继承本质上还是基于原型链的继承,所以也是不支持多继承的。但就像java那样,我们也可以通过其他方式达到相同或者类似的效果。

// class
class Person {
  constructor() {
    this.type = 'Person'
    this.age = '18'
  }
  sayName() {
    console.log('说话')
  }
}
class Animal {
  constructor() {
    this.type = 'Animal'
    this.age = '19'
  }
  eat() {
    console.log('进食')
  }
}

// mixin
// 多重继承 一个子类继承多个父类
function mixin(...mixins) {
  class Mix {
    constructor() {
      for(let mixin of mixins) {
        // 拷贝实例属性
        copyProperties(this, new mixin())
      }
    }
  }
  for(let mixin of mixins) {
    copyProperties(Mix, mixin) // 拷贝静态属性
    copyProperties(Mix.prototype, mixin.prototype) // 拷贝原型属性
  }
  return Mix
}
function copyProperties(target, source) {
  // Reflect.ownKeys 返回所有属性key
  // Object.keys 返回属性key,不包括不可枚举属性
  for(let key of Reflect.ownKeys(source)) {
    if (key !== 'constructor' && key !== 'prototype' && key !== 'name') {
      // Object.getOwnPropertyDescriptor 返回指定对象上一个自有属性对应的属性描述符。
      // 自有属性指的是直接赋予该对象的属性,不需要从原型链上进行查找的属性
      // 属性描述符指的是configurable、enumerable、writable、value这些
      const desc = Object.getOwnPropertyDescriptor(source, key)
      Object.defineProperty(target, key, desc)
    }
  }
}
class Other extends mixin(Animal, Person) {}
const oo = new Other()
console.log(oo)

在这里插入图片描述
通过以上方式,也可以间接地实现类似多重继承的效果。

参考文献

《ES6 入门教程》 阮一峰
《深入理解es6》Nicholas C. Zakas
MDN

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐