类(class)

186 阅读4分钟

前言

在ES5中,生成实例对象的传统方法是通过构造函数,如:

function Rectangle (height, width) {
  this._height = height;
  this._width = width;
}

Rectangle.prototype.calculate = function () {
  return this._height * this._width;
};

const rect1 = new Rectangle(1, 2);
rect1.calculate(); // 2

在ES6中,class (类)是用于创建对象的模板,可以通过 class 关键字定义类。

class 的本质是 function。

它可以看作一个语法糖,让对象原型的写法更加清晰、更像面向对象编程的语法。

class Rectangle {
  constructor(height, width) {
    this._height = height;
    this._width = width;
  }
  calculate() {
      return this._height * this._width;
  }
}
const rect2 = new Rectangle(2, 2);
rect2.calculate(); // 4
typeof Rectangle // "function"

基础用法

两种方式定义类:类声明类表达式

类声明

  • 用带有class关键字的类名进行声明
  • 对类使用new命令生成实例对象

类表达式

类表达式可以命名或不命名,可以通过类的 (而不是一个实例的) name 属性来检索它。

// 未命名/匿名类
let Rectangle = class {
  constructor(height, width) {
    this._height = height;
    this._width = width;
  }
};
console.log(Rectangle.name); // "Rectangle"

// 命名类
let Rectangle = class Rectangle2 {
  constructor(height, width) {
    this._height = height;
    this._width = width;
  }
};
console.log(Rectangle.name); // "Rectangle2"

类声明类表达式不会提升,首先需要声明,然后再访问,否则抛错ReferenceError

主体

属性

prototype属性

ES6 中,prototype 仍旧存在,虽然可以直接在类中定义方法,但是其实方法还是定义在 prototype 上的。因此,在类的实例上面调用方法,其实就是调用原型上的方法。

class Rectangle {}
const rect = new Rectangle();

rect.constructor === Rectangle.prototype.constructor // true

name属性

返回跟在 class 后的类名(存在时)。

实例属性

constructor()方法里面的this上面定义

class Rectangle {
  constructor(height, width) {
    this._height = height;
    this._width = width;
  }
}

静态属性

静态属性指的是 Class 本身的属性, 即Class.propname, 而不是定义在实例对象( this) 上的属性,而是定义在类的外面

Rectangle.staticWidth = 20;

ES6 明确规定, Class 内部只有静态方法, 没有静态属性。

ES7 有一个静态属性的提案, 目前 Babel 转码器支持:

  • 实例属性

ES6中定义实例属性, 只能写在类的constructor方法里面。

class ReactCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
}

ES7中, 可以这样写:

class ReactCounter extends React.Component {
  state = {
    count: 0
  };
}

为了增强可读性, 对于那些在constructor里面已经定义的实例属性,还可以这样:

class ReactCounter extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  state;
}
  • 静态属性

ES6的静态属性定义在类的外部,很容易被忽略。

ES7中, 可以这样写:

class Rectangle {
  static staticWidth = 20;
  constructor(height, width) {
    this._height = height;
    this._width = width;
  }
}

方法

constructor方法

  • constructor方法是类的默认方法,创建类的实例化对象时被调用。
  • constructor方法默认返回实例对象(即this),也可以指定返回另外一个对象。

继承

extends

  • 子类使用 extends 关键字实现继承

super

  • 子类中使用 super 关键字来调用父类的构造函数和方法

继承的 .prototype 必须是一个 Object 或者 null

class Car {
  constructor(brand) {
    this.carname = brand;
  }
  present() {
    return 'I have a ' + this.carname;
  }
}

class Model extends Car {
  constructor(brand, mod) {
    super(brand);
    this.model = mod;
  }
  show() {
    return this.present() + ', it is a ' + this.model;
  }
}

const mycar = new Model("Ford", "Mustang");

1.png

存取器

gettersetter

  • getter 不可单独出现
  • gettersetter 必须同级出现
  • gettersetter 是设置在属性的 Descriptor 对象上的
class CustomHTMLElement {
  constructor(element) {
    this.element = element;
  }

  get html() {
    return this.element.innerHTML;
  }

  set html(value) {
    this.element.innerHTML = value;
  }
}

var descriptor = Object.getOwnPropertyDescriptor(
  CustomHTMLElement.prototype, "html"
);

"get" in descriptor  // true
"set" in descriptor  // true

以上代码的 gettersetter 是定义在html属性的描述对象上的。

修饰符

 访问修饰符(Access Modifiers): publicprivate 和 protected

  • public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有属性和方法都是 public 
  • private 修饰的属性或方法是私有的,只能在声明它的类的内部访问
  • protected 修饰的属性或方法是受保护的,能在类和类的子类使用

readonly

使用 readonly关键字设置只读属性,必须在声明时或构造函数里被初始化。

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

const a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom'; // error

static

使用 static 修饰符修饰的属性称为静态属性,只能在类中使用 使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用

abstract

abstract 用于定义抽象类和其中的抽象方法。

抽象类

  • 抽象描述一种概念,做为其它类的基类使用
  • 无法创建抽象类的实例,抽象类只能被继承

抽象方法

  • 抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现
  • 抽象方法只能出现在抽象类中
abstract class Animal {
  public name;
  public constructor(name) {
    this.name = name;
  }
  public abstract sayHi();
}

const a = new Animal('Jack'); // error
class Cat extends Animal {
  public eat() {
    console.log(`${this.name} is eating.`);
  }
}
const cat = new Cat('Tom'); // error: Non-abstract class 'Cat' does not implement inherited abstract member 'sayHi' from class 'Animal'.

上面的例子中,Cat 继承了抽象类 Animal,但是没有实现抽象方法 sayHi,编译报错。

如何正确使用抽象类:

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

class Cat extends Animal {
  public sayHi() {
    console.log(`Meow, My name is ${this.name}`);
  }
}

const cat = new Cat('Tom');
cat.sayHi(); // Meow, My name is Tom

类的类型

给类加上 TypeScript 的类型:

class Animal {
  name: string;
  constructor(name: string) {
    this.name = name;
  }
  sayHi(): string {
    return `My name is ${this.name}`;
  }
}

let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack

有个疑问:

privatestatic 修饰的属性,有区别吗?

参考:

TypeScript Class

MDN Class