你真的了解Class吗?

382 阅读7分钟

==这篇文章将会让你了解class的基本使用,文章的代码尤为重要==

什么是Class

class是ES6提出的一种概念,通过class关键字来定义类,他其实是创建构造函数的一种语法糖,通过class来创建,使语法更加明了

通过基类创建

基类是使用'class'关键字定义的意思

class Person {
  constructor (name) {
      // 给类的实例添加一个name属性
      this.name = name;
  }
  // 在prototype中添加say方法
  say () {
      console.log(this) // Person实例
      console.log('我的名字叫遥近')
  }
}


// 上面的代码其实等价于此
function Person (name) {
    this.name = name
}

Person.prototype.say = function () {
  console.log('我的名字叫遥近')
}
 

constructor不是必须要写的,如果您需要为实例添加属性和方法,那么就需要,但是constructor又是必须的,哪怕你没有写上,它也会默认添加constructor

基类的默认constructor

constructor() {}

通过类表达式创建

通过类表达式创建,可以是命名的也可以是匿名的

==创建一个匿名的类==

 let Person = class {
     who () {
        return '遥近'
     }
 }
 let yaojin = new Person()
console.log(yaojin.who()) // 遥近

==创建一个命名类==

let Iphone = class phone {
    say () {
      console.dir(phone)
      console.log(phone.name) // phone
    }
  }

  let iphone11 = new Iphone()
  iphone11.say()

通过命名类或者基类创建,都可以在==内部==使用类本身 可以看到 console.dir(phone) 可以获取到类本身,而通过匿名创建,没有名称无法读取类自身

==通过类表达式,可以写出立即执行的 Class==

let Iphone = new class phone{
    constructor (name) {
      this.name = name
    }
  }('iphone11')
  console.log(Iphone.name) // iphone11

创建class的静态方法

在class中定义的方法都会在原型上,如果在一个方法前加上 ==static== 关键字,那么该方法就不会给继承,==只能通过类自身来调用,静态方法中的this指向类本身==

class Phone{
  static money () {
    console.log(9999)
  }
}
Phone.money() // 9999
let iphone11 = new Phone()
iphone11.money() // 报错了

创建class的静态属性

跟静态方法一样,静态属性只能 ==类自身使用== 其实==es6并不支持静态属性==, 因为类也是对象,其实我们只是在这个对象上添加了一个prop属性,勉强可以说是静态属性,可是如果你将方法一的prop属性名改成name,其实它并不会改变类自身的name,打印的时候还是原来的name值

而方法二仅仅只是草案, 通过方法二定义的可以改变自身的name值,是真正意义上的静态属性

  // 方法一
class Phone{
}
Phone.prop = 'iphone11'
console.log(Phone.prop) // iphone11
// // 方法二
class Phone {
  static name = 'iphone11'
}
let iphone11 = new Phone()
console.log(Phone.name) // iphone11
console.log(iphone11.name) // undefined

简单的定义实例属性

如果想给实例添加一个属性,我们一般会在constructor中进行定义,还有更加简单的办法,直接写在class的==最顶部(更加规范)==

class Phone{
  name = 'iphone11'
}
let iphone11 = new Phone()
console.log(iphone11.name) // iphone11

当然这种办法,仅适合不是动态的设置实例的属性的时候,否则的话还是使用constructor吧

创建私有方法

所谓私有方法是只能在==类内部使用的方法==,外部无法调用,可是ES6并不提供,如果我们想实现这样的需求,那么就需要自己模拟实现

==通过改变this指向模拟==

class Iphone {
    price (num) {
        buy.call(this, num)
    }
}
function buy (num) {
    return this.money = num // 给实例对象添加了一个money属性
}

let iphone11 = new Iphone()
iphone11.price(9999)
console.log(iphone11.money) // 9999

Class继承(派生类)

Class 可以通过extends关键字实现继承

class MobilePhone {
  constructor(price) {
    this.price = price
  }
  call () {
    console.log('开始通信')
  }
}
class Iphone extends MobilePhone {
  constructor (price, name) {
    super(price) // 调用父类constructor(price)
    this.name = name
  }
  call () {
    super.call()
  }
}
let iphone11 = new Iphone(9999)
iphone11.call() // 开始通信
console.log(iphone11.price)

==需要注意的是,实现继承子类必须在constructor中调用super方法才可以使用this==, 这是因为你要继承父类的属性和方法,如果你没有调用该方法,就会报错

我们之前也说了,constructor是必须的,你可以不显示定义,如果你没有显示定义,它也会默认添加

继承(派生类)默认的constructor

constructor(...args) {
  super(...args);
}

通过继承的创建的实例对象, 都是两个类的实例

console.log(iphone11 instanceof Iphone) // true
console.log(iphone11 instanceof MobilePhone) // true

另外, 子类还可以继承父类的静态方法和静态属性

class MobilePhone {

  static number = 1
  static call () {
    console.log('开始通信')
  }
}
class Iphone extends MobilePhone {
}
Iphone.call()
console.log(Iphone.number)

super的使用

super即可当函数使用也可以当对象使用

作为函数使用只能在constructor中使用,在其他地方会报错,并且其this指向子类的构造函数 ==也就是说当super当函数使用的使用,其实等价于 父类.prototype.constructor.call(this)==

当作为对象使用的时候,在普通方法中,指向父类的原型对象;在静态方法中,指向父类。

普通方法

class MobilePhone {
  call () {
    console.log(this) // Iphone实例
    console.log('开始通信')
  }
}
class Iphone extends MobilePhone {
  constructor () {
    super()
    this.money = 9999
    super.money = 1 // 此时super为this
    console.log(super.money) // undefined
    console.log(this.money)
  }
  inherit () {
    super.call() // 普通方法中
  }
}
console.log(Iphone.prototype)
let iphone11 = new Iphone()
iphone11.inherit() // 开始通信,调用了父的方法
  1. 当在子类的普通方法中调用super,此时方法中的this指向当前子类的实例
  2. 当对某个属性进行赋值的时候,super就是this
  3. 其他情况super为父类的原型对象,可以看到当用super.money读取的时候,是undefined,因为此时是读取父类的prototype,显然是没有money属性

静态方法

class MobilePhone {
  static call () {
    console.log('我是手机')
  }
}
class Iphone extends MobilePhone {
  static call () {
    super.call() // MobilePhone.call
  }
}
Iphone.call() // 我是手机

当在静态方法中使用super,此时super为父类

如何模拟实现继承?

首先我们要明确一个关系如下:

class MobilePhone {
}
class Iphone extends MobilePhone {
}
Iphone.__proto__ === MobilePhone
Iphone.prototype.__proto__ === MobilePhone.prototype

这样的结果是通过这样进行实现的

class MobilePhone {
}

class Iphone {
}

// Iphone 的实例继承 MobilePhone 的实例
Object.setPrototypeOf(Iphone.prototype, MobilePhone.prototype);

// Iphone 的实例继承 MobilePhone 的静态属性
Object.setPrototypeOf(Iphone, MobilePhone);

什么是setPrototypeOf ? setPrototypeOf 的实现

Object.setPrototypeOf = function (obj, proto) {
  obj.__proto__ = proto;
  return obj;
}

extends后面可以跟多种类型

了解到时如何继承,那么我们就可以知道extends不仅仅可以继承class定义的类,只要是函数那么就可以继承, 因为函数拥有prototype属性,当然==Function.prototype除外==

例如我创建的class想继承Object原型上的方法

class Iphone extends Array {
}
Iphone.__proto__ === Array
Iphone.prototype.__proto__ === Array.prototype

应用场景 现在有个业务需求,设置病人的体检项目,病人有默认的体检项目有抽血、内外科检查,可是根据不同的情况又会添加不一样的体检项目,此时我们就可以封装一个class了

class Patient extends Array {
  constructor () {
    super()
    this.checkList = ['抽血', '内外科检查']
  }
  addProject (name) {
    this.checkList.push(name)
  }
}
let xiaogao = new Patient()
xiaogao.addProject('手术')
console.log(xiaogao.checkList) // ["抽血", "内外科检查", "手术"]

Mixin模式的实现

Mixin 指的是多个对象合成一个新的对象,这样你定义的Class就可以拥有多种继承对象

function mix(...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) {
  // 遍历所有构造函数,返回构造函数的自身属性
  for (let key of Reflect.ownKeys(source)) {
    // 排除一些没有的属性
    if ( key !== 'constructor'
      && key !== 'prototype'
      && key !== 'name'
    ) {
      // 获取对象的属性描述符
      let desc = Object.getOwnPropertyDescriptor(source, key);
      // 给对象上定义一个新的属性
      Object.defineProperty(target, key, desc);
    }
  }
}

class DistributedEdit extends mix(Array, String) {
}

new target

ES6 为new命令引入了一个new.target属性

function Person(name) {
  if (new.target !== undefined) {
    this.name = name;
  } else {
    throw new Error('必须使用 new 命令生成实例');
  }
}
var person = new Person('张三'); // 正确
var notAPerson = Person.call(person, '张三');  // 报错

细节知识

==constructor默认返回实例对象,但是可以指定返回一个新的对象==

class Foo {
  constructor() {
    return Object.create(null);
  }
}

new Foo() instanceof Foo // false

上面代码中,constructor函数返回一个全新的对象,结果导致实例对象不是Foo类的实例

==类的属性名,可以采用表达式。==

let methodName = 'getArea';

class Square {
  constructor(length) {
    // ...
  }

  [methodName]() {
    // ...
  }
}

==类不存在变量提升==

new Foo(); // ReferenceError
class Foo {}