JavaScriptES6中的类和继承

624 阅读6分钟

本文由我们团队肖建朋学习后总结

实际上是个“特殊的函数”,就像你能够定义的函数表达式函数声明一样,类语法有两个组成部分:类表达式和类声明。

一个类的类体是一对花括号/大括号 {} 中的部分。这是你定义类成员的位置,如方法或构造函数。

类声明

定义一个类的一种方法是使用一个类声明。可以使用带有class关键字的类名。

class User {
    constructor() {
        this.name = 'A'
        this.age = 20
    }
}

函数声明类声明之间的一个重要区别是函数声明会提升,类声明不会。你首先需要声明你的类,然后访问它,否则像下面的代码会抛出异常 。如果我们想要使用上面的User类,那么我们需要先声明User

let user = new User()
console.log(user.name)

类表达式

一个类表达式是定义一个类的另一种方式。类表达式可以是被命名的或匿名的。赋予一个命名类表达式的名称是类的主题和本地名称。我们以User为例,分别定义它的匿名类和命名的类

//匿名类
let user = class {
    constructor() {
        this.name = 'A'
        this.age = 20
    }
}
// 命名类
let user = class User {
    constructor() {
        this.name = 'A'
        this.age = 20
    }
}

类声明和类表达式的主体都执行在严格模式sloppy mode 下。比如,构造函数,静态方法,原型方法,getter和setter都在严格模式下执行。

构造函数

constructor方法是一个特殊的方法,其用于创建和初始化使用class创建的一个对象,在new一个对象时,自动调用该方法 。一个类只能拥有一个名为 “constructor”的特殊方法。如果类包含多个constructor的方法,则将抛出 一个SyntaxError

一个构造函数可以使用 super 关键字来调用一个父类的构造函数。

原型方法

class User {
    constructor(firstName, lastName, age) {
        this.firstName = firstName
        this.lastName = lastName
        this.age = age
    }
    // Getter
    get fullname() {
        return this.getFullName()
    }
    // method
    getFullName() {
        return this.firstName + this.lastName
    }
}
let user = new User('wang', 'xiao', 23)
console.log(user.fullname)

静态方法

static关键字用来定义一个类的静态方法。调用静态方法不用实例化该类,但不能通过一个类实例调用静态方法。

class tools {
    static add (numA, numB) {
        return numA + numB
    }
}
let result = tools.add(1,3)
console.log(result) // 4

使用extends创建子类

extends关键字在类声明或类表达式中用于创建一个类作为另一个类的子类。请注意,类不能继承常规(非可构造)对象。如果要继承常规对象,可以改用Object.setPrototypeOf()

class Animal { 
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Dog extends Animal {
  speak() {
    console.log(this.name + ' barks.');
  }
}

var d = new Dog('Mitzie');
// 'Mitzie barks.'
d.speak();

使用super调用超类

super关键字用于调用对象的父对象上的函数。

class Cat { 
  constructor(name) {
    this.name = name;
  }

  speak() {
    console.log(this.name + ' makes a noise.');
  }
}

class Lion extends Cat {
  speak() {
    super.speak();
    console.log(this.name + ' roars.');
  }
}

Mix-ins

抽象子类或者 mix-ins 是类的模板。 一个 ECMAScript 类只能有一个单超类,所以想要从工具类来多重继承的行为是不可能的。子类继承的只能是父类提供的功能性。因此,例如,从工具类的多重继承是不可能的。该功能必须由超类提供。

一个以超类作为输入的函数和一个继承该超类的子类作为输出可以用于在ECMAScript中实现混合:

var calculatorMixin = Base => class extends Base {
  calc() { }
};

var randomizerMixin = Base => class extends Base {
  randomize() { }
};

使用 mix-ins 的类可以像下面这样写:

class Foo { }
class Bar extends calculatorMixin(randomizerMixin(Foo)) { }

模版方法模式

定义和组成

模板方法模式是一种只需使用继承就可以实现的非常简单的模式。

模板方法模式由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常
在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺
序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。

模版方法的使用场景:

假如我们有一些平行的子类,各个子类之间有一些相同的行为,也有一些不同的行为。如果
相同和不同的行为都混合在各个子类的实现中,说明这些相同的行为会在各个子类中重复出现。
但实际上,相同的行为可以被搬移到另外一个单一的地方,模板方法模式就是为解决这个问题而
生的。在模板方法模式中,子类实现中的相同部分被上移到父类中,而将不同的部分留待子类来
实现。

这个例子是使用realm在本地存储数据(点击查看realm官方文档).

realm安装

npm install --save realm

link

react-native link realm

###父类

构造realmHelper类,封装了数据存储需要的save、delete和关闭realm实例的close方法。

import Realm from 'realm'

class RealmHelper {
  constructor(schema) {
    this.schema = schema
    this.realm = new Realm({schema, schemaVersion: 1})
  }

  /**
   * 添加记录
   * @param {{}} obj= {name:'对象名', data:[{}], update:false},update为true时根据主键id更新数据
   */
  save(obj) {
    try {
      if(!obj.objName) {
        obj.objName = this.schema.name
      }
      console.log(this.realm)
      this.realm.write(() => {
        obj.data.forEach(element => {
          this.realm.create(obj.objName, element, obj.update)
        })
        const resList = this.realm.objects(obj.objName)
        console.log(resList)
      })
    } catch (error) {
      throw error
    }
  }

  delete(objName, filters) {
    if(!objName) {
      objName = this.schema.name
    }
    console.log('开始删除',  this.realm)
    this.realm.write(() => {
      let deleteUsers
      if(filters) {
        // 根据条件获取删除对象
        deleteUsers = this.realm.objects(objName).filtered(filters )
      } else {
        deleteUsers = this.realm.objects(objName)
      }
      this.realm.delete(deleteUsers)
      const result = this.realm.objects(objName)
      console.log(result)
    })
  }

  close() {
    this.realm.close()
  }
}

export default RealmHelper

子类

User 使用extends实现继承父类RealmHelper,在constructor中调用super函数(相当于调用父类的构造函数,获得父类的this)

子类有constructor

子类必须在constructor方法中调用super方法,否则new实例时会报错。因为子类没有自己的this对象,而是继承父类的this对象。如果不调用super函数,子类就得不到this对象。super()作为父类的构造函数,只能出现在子类的constructor()中;但是super指向父类的原型对象,可以调用父类的属性和方法

import RealmHelper from './RealmHelper'

const UserSchema = {
  name: 'User',
  primaryKey: 'id', // 主键
  properties: {
    id: 'string', // 用户Id
    username: 'string', // 用户名
    orgLabel: 'string', // 组织名称
    department: 'string', // 部门
  },
}
class User extends RealmHelper {
  constructor(schema) {
    super()
  }
  aboutMe() {
      console.log('我是子类user')
  }
}

实例化和调用

实例化子类对象时,子类对象可以拥有父类的属性和方法,子类对象还可以拥有自己的属性和方法。比如User继承了父类,还拥有自己的aboutMe方法。

let user = new User([UserSchema])
console.log(user.schema)
const obj = {
    update: false,
    data: [
        {
          id: dayjs().valueOf().toString(), // 用户Id
          username: 'Tom'+dayjs().valueOf().toString(), // 用户名
          orgLabel: '集团管理中心', // 组织名称
          department: '系统部',
        }
       ],  
}
user.save(obj)
user.aboutMe()

总结

模版方法模式通过将不变的逻辑抽象到父类模版方法中,将变化的部分逻辑封装在子类中。可以提高系统的扩展性和减少代码的耦合。通过增加新的子类,我们便能给系统增加新的功能,并不需要改动抽象父类以及其他子类,这也是符合开放.封闭原则的。