JS高级 - ES6中的继承

227 阅读5分钟

在ES6之前,创建类使用的是构造函数和原型链,这种编写方式和编写普通的函数过于相似,因此代码并不容易理解和区分

在ES6(ECMAScript2015)新的标准中使用了class关键字来直接定义类

但是类本质上依然是前面所讲的构造函数、原型链的语法糖而已

只不过相比ES5中使用构造函数来定义类,在ES6中使用class来定义类的时候,对原本的定义方式又做了一定程度的增强

创建方式

// 类声明
// 类的首字母一般大写
class Person {}

// 类表达式
const Student = class {}

基本使用

// class中的大括号是一种新的用于定义类的方法
// 所以在类中的方法和方法之间不需要使用逗号进行分割
class Person {
  // 在使用new关键字调用Person类的时候
  // 默认会调用类的构造方法进行属性的初始化操作
  // 如果没有显示定义类的构造方法的时候,默认会存在一个空的构造方法
  constructor() {} // -> 这是默认的构造方法

  // funName() {} 这种定义方式是类中固定语法
  // 并不是funName: function() {} 这种形式的语法糖

  // 在ES6中 类的构造函数最多只能存在一个,不可重复定义
}
// class定义类将类的属性和方法封装在了一个大括号中
// 相比使用构造函数定义类,提高了内聚度,降低了耦合度
class Person {
  // 类中的构造函数的运行方式和ES5中构造函数的运行方式是一致的
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  // 在类中定义的方法默认会被放置到类的原型对象上
  running() {
    console.log('running')
  }

  // 在类中也可以使用如下方式定义方法和属性
  // 这种定义类的属性和方法的定义方式被称之为类的表达式
  eatting = () => {
    console.log('eatting')
  }
}

const per = new Person('Klaus', 23)
per.running()
per.eatting()

ES6中的constructor函数和实例方法本质上就是对ES5使用构造函数来定义类的形式的一种语法糖形式

唯一的区别是ES5中的构造函数即可以使用new关键字来进行调用,也可以作为一个普通的函数来进行调用

而ES6中的class类只能够使用new关键字来进行调用,不可以作为一个普通函数来进行调用

访问器(或叫存储器)

class Foo {
  // 类的私有属性一般使用类的表达式方式来进行定义
  // 因为类的私有属性是不可以放置到实例对象上的
  // 也就是说不可以挂载到this上,也就不可以在构造函数中对其进行定义
  #name = ''

  constructor() {
    // 在ES6之前,定义私有属性和方法使用的是_开头
    // 但这种只是大家约定俗称的一种方法,并没有在语法层面上去其进行限制
    // 在ES13中提供了一种新的在语法层面上定义私有属性和私有方法的语法
    // 即当一个类的属性或方法使用#开头的时候,就表示该属性或方法是类的私有属性或私有方法
  }

  // getter方法
  get name() {
    return this.#name
  }

  // setter方法
  set name(v) {
    this.#name = v
  }
}

const foo = new Foo()
foo.name = 'Klaus'
console.log(foo.name)

静态方法和静态属性

静态方法通常用于定义直接使用类来执行的方法,不需要有类的实例,使用static关键字来定义

class Foo {
  static username = 'klaus'

  static bar() {
    console.log(this) // => class Foo
  }
}

console.log(Foo.username) // => klaus
Foo.bar()

继承

在ES6中新增了使用extends关键字,可以方便的帮助我们实现继承

其本质依旧会被转换为ES5的寄生组合式继承,extends是对于ES5中寄生组合式继承的语法糖

class Parent {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  running() {
    console.log('running')
  }
}

// 使用extends关键字实现类和类之间的继承
class Student extends Parent {
  constructor(name, age, sno) {
    super(name, age)
    this.sno = sno
  }

  eatting() {
    console.log('eatting')
  }
}

const stu = new Student('Klaus', 23, 1810166)
console.log(stu)
stu.running()
stu.eatting()

super

class Person  {
  constructor() {
  }
}

class Student extends Person {
  // 继承后 对应的默认构造函数如下:
  constructor() {
    // 在使用this之前或返回实例对象之前
    // 必须调用super方法初始化实例对象
    super()
  }
}
class Person {
  constructor(name, age) {
    this.name = name
    this.age = age
  }

  running() {
    console.log('running')
  }

  static eatting() {
    console.log('eatting')
  }
}

// super的使用位置有三个: 子类的构造函数、实例方法、静态方法
class Student extends Person {
  constructor(name, age) {
    // 在子(派生)类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数
    super(name, age)
  }

  // 子类重写父类的实例方法
  running() {
    console.log('overwrite')
    super.running()
  }

  // 子类重写父类的静态方法
  static eatting() {
    console.log('overwrite')
    super.eatting()
  }
}

const stu = new Student('Klaus', 24)
Student.eatting()
stu.running()

继承内置类

我们也可以让我们的类继承自内置类,比如Array,Math等, 以便于对内置类的功能进行扩展

// 继承内置类方式一
// 重写一个新的类来在继承的同时添加新的属性和方法
class MyArr extends Array {
  get lastItem() {
    return this[this.length - 1]
  }

  get firstItem() {
    return this[0]
  }
}

const arr = new MyArr(10, 20, 30)

console.log(arr.lastItem) // => 30
console.log(arr.firstItem) // => 10
// 继承内置类方式二 - 在原有的类上添加新的属性和方法
Array.prototype.lastItem = function() {
  return this[this.length - 1]
}

Array.prototype.firstItem = function() {
  return this[0]
}

const arr = new Array(10, 20, 30)

console.log(arr.lastItem()) // => 30
console.log(arr.firstItem()) // => 10

mixin

mixin 本质是一种叫组合模式的设计模式,它通过组合多个功能模块来扩展对象的行为,而不是通过继承来扩展。

mixin 通常通过将一组方法或属性添加到现有对象上来实现扩展。从而在不改变原始对象的情况下添加新功能。

虽然mixin相比继承来说更灵活也更轻量级,但是如果使用了多个mixin,且多个mixin之间 包含同名的属性或方法,后者会覆盖前者。这会导致属性和方法的来源不明确,使代码难以理解和维护,增加调试的复杂性

因此一般不推荐使用mixin

示例

JavaScript默认只支持单继承,但可以通过mixin来模拟多继承

class Student {
  studying() {
    console.log('studying')
  }
}

// mixin模拟多继承的基本原理就是将多继承转变为多层的单继承
function mixinEatting(baseClass) {
  return class extends baseClass {
    eatting() {
      console.log('eatting')
    }
  }
}

function mixinRunning(baseClass) {
  return class extends baseClass {
    running() {
      console.log('running')
    }
  }
}

const stu1 = new (mixinEatting(mixinRunning(Student)))()
stu1.running()
stu1.eatting()
stu1.studying()