面向对象(五):ES6 类的基本使用

171 阅读3分钟

JavaScript 对象系列

面向对象(一):认识对象

面向对象(二):认识JavaScript中对象的原型

面向对象(三):创建多个对象的方案

面向对象(四):掌握原型链

面向对象(五):ES6 类的基本使用

面向对象(六):JavaScript中的7种继承方式

面向对象(七):ES6的class转ES5的源码阅读

第五篇

ES6 中 提供的类 (class)其实是 ES5 之前构造函数、原型、原型链的语法糖。

定义方式

class Person {}   // 类的申明
let Person = class {} // 类的表达式(很少使用)

保留 ES5 的特性

class Person {}
​
console.log(Person.prototype)  // {} 函数原型
console.log(Person.prototype.__proto__)  // [Object: null prototype] {} 顶层原型
console.log(Person.prototype.constructor) // [class Person]
console.log(typeof Person) // function 本质上还是函数let p = new Person()
console.log(p.__proto__ === Person.prototype)  // true

类的传参

// 类的申明
class Person {
  // 类的构造方法(接受参数)
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
​
let p = new Person("copyer", 18);

注意:一个类里面只有一个构造方法(constructor),多了就会报错。

当使用 new 关键词 的时候,就会调用类的构造方法。

类的方法(普通实例方法)

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  // 类的方法
  play() {
    console.log(this.name + " playing~");
  }
}
​
let p = new Person("copyer", 18);

类的方法是放在类的原型上的,所以能实现方法共享。

如何证明呢?

// 使用Object提供的方法,来获取对象上的属性
console.log(Object.getOwnPropertyDescriptors(Person.prototype))
/**
{
  constructor: {
    value: [class Person],
    writable: true,
    enumerable: false,
    configurable: true
  },
  play: {  // 说明存在Person的函数原型上
    value: [Function: play],
    writable: true,
    enumerable: false,
    configurable: true
  }
}
*/

类的访问器

class Person {
  constructor() {
    this._name = "copyer";
  }
  // 类的访问器
  get name() {
    console.log("获取值之前的拦截");
    return this._name;
  }
​
  set name(newValue) {
    console.log("设置属性值之前的拦截");
    this._name = newValue;
  }
}

可以实例化,验证一下

let p = new Person()
console.log(p.name)  // 获取值之前的拦截   copyer
p.name = 'james'     // 设置属性值之前的拦截
console.log(p.name)  // 获取值之前的拦截   james

作用:进行获取和设置的拦截。

类的静态方法

class Person {
  constructor() {
    this._name = "copyer";
  }
  // 类的静态方法
  static createPerson() {
    return new Person('james', 18)
  }
}

通过关键词 static 来定义

Person.createPerson() // 只能构造函数.的形式调用

类的继承

class 中 类的继承很简单, 使用 extends 即可。

// 父类
class Person {
  
}
​
// 子类继承父类: extends
class Student extends Person {
​
}

继承属性应该怎么写呢

// 父类
class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
​
// 子类继承父类: extends
class Student extends Person {
  constructor(name, age, address) {
    this.name = name;
    this.age = age;
    this.address = address;
  }
}
​
var s = new Student("copyer", 12, "cq");

可以这样写吗?答案是不可以的

报错信息

ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

简单理解就是:在使用 this 之前,应该在构造函数中先使用 super 关键词。

解释

JavaScript引擎在解析子类的时候,是有要求的。如果存在继承关系的话,如果使用了this,就必须先使用 super,才能正常的解析。

总结:

在子类的构造函数中使用this或者返回默认对象之前,必须先通过super调用父类的构造函数 。

super关键词

上面提及到了 super ,那么应该怎么使用呢?

使用方式一:调用父类的构造函数

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
}
​
class Student extends Person {
  constructor(name, age, address) {
    super(name, age)  // 类似于借用构造函数继承 Person.call(this, name, age)
    this.address = address;
  }
}
​
var s = new Student("copyer", 12, "cq");

子类中的构造函数调用 super 方法,就是借用了父类中的构造函数中逻辑

方式二:重写类方法

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
  
  // 类的方法
  play() {
    console.log("父类的逻辑");
  }
}
​
class Student extends Person {
  constructor(name, age, address) {
    super(name, age);
    this.address = address;
  }
  // 重写父类的方法
  play() {
    super.play(); // 需求:先执行父类的逻辑,然后在执行子类的逻辑
    console.log("子类的逻辑");
  }
}

方式二:重写静态方法

class Person {
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }
​
  // 类的静态方法
  static play() {
    console.log("父类的逻辑");
  }
}
​
class Student extends Person {
  constructor(name, age, address) {
    super(name, age);
    this.address = address;
  }
  // 重写类的静态方法
  static play() {
    super.play(); // 需求:先执行父类的逻辑
    console.log("子类的逻辑");
  }
}
​
Student.play()