# 八、类(Class)
TypeScript 完全支持 ES2015 引入的 class 关键字。
# 1.基本用法
class Point {
x: number; // 属性“x”没有初始化表达式,且未在构造函数中明确赋值。
o!: string; // 你可以使用明确赋值断言操作符,来消减上面的错误(definite assignment assertion operator) !:
y; // 成员“y”隐式包含类型“any”。
z = 1; // Point.z: number 初始值的设置,会让Ts自动推断出它的类型
}
const point = new Point();
point.z = '1'; // 不能将类型“string”分配给类型“number”。
# 2.类属性
readonly 字段可以添加一个 readonly 前缀修饰符,这会阻止在构造函数之外的赋值。
class Point {
readonly r: number
constructor(){
this.r = 100;
}
setR(){
this.r = 200; // 无法分配到 "r" ,因为它是只读属性。
}
}
const point = new Point();
point.r = 200; // 无法分配到 "r" ,因为它是只读属性。ts
Constructors 方法是类的默认构造函数方法,一个类必须有constructor()方法,如果没有显式定义,一个空的constructor()方法会被默认添加。 构造函数返回类实例类型
class Point {
x: number;
y: number;
// Normal signature with defaults
constructor(x = 0, y = 0) {
this.x = x;
this.y = y;
}
}
取值函数(getter)和存值函数(setter)
在“类”的内部可以使用get和set关键字,对某个属性设置存值函数和取值函数,拦截该属性的存取行为。
class Point {
get prop() {
return "getter";
}
set prop(value) {
console.log("setter: " + value);
}
}
const inst = new Point();
inst.prop = '123'; // setter: 123
console.log(inst.prop); // getter
TypeScript 对存取器有一些特殊的推断规则:
- 如果
get
存在而set
不存在,属性会被自动设置为readonly
- 如果
setter
参数的类型没有指定,它会被推断为getter
的返回类型
# 3.类的继承
Class 可以通过extends关键字实现继承,让子类继承父类的属性和方法。extends 的写法比 ES5 的原型链继承,要清晰和方便很多。
class Point {
}
class ColorPoint extends Point {
}
ColorPoint 作为子类继承了 Point 的所有属性方法,当然目前两个类都是空的
# 3.1 super
super这个关键字,既可以当作函数使用,也可以当作对象使用。在这两种情况下,它的用法完全不同。
第一种情况,super作为函数调用时,代表父类的构造函数。ES6 要求,子类的构造函数必须执行一次super函数。
class Foo {
constructor() {
console.log(1);
}
}
class Bar extends Foo {
constructor() {
super();
console.log(2);
}
}
const bar = new Bar();
// 1
// 2
为什么子类的构造函数,一定要调用super()?原因就在于:ES6 的继承机制,则是先将父类的属性和方法,加到一个空的对象上面,然后再将该对象作为子类的实例,即“继承在前,实例在后”。这就是为什么 ES6 的继承必须先调用super()方法,因为这一步会生成一个继承父类的this对象,没有这一步就无法继承父类。
可见此时的执行顺序是:
- 基类字段初始化
- 基类构造函数运行
- 派生类字段初始化
- 派生类构造函数运行
第二种情况,super作为对象时,在普通方法中,指向父类的原型对象;在静态方法中,指向父类
class Base {
greet() {
console.log("Hello, world!");
}
}
class Derived extends Base {
greet(name?: string) {
if (name === undefined) {
// super: class Base
super.greet();
} else {
console.log(`Hello, ${name.toUpperCase()}`);
}
}
}
const d = new Derived();
d.greet();
d.greet("reader");
# 3.2 implements
你可以使用 implements 语句检查一个类是否满足一个特定的 interface。如果一个类没有正确的实现(implement)它,TypeScript 会报错:
interface classT {
func1(): string;
}
class example1 implements classT {
func1() {
return "str";
}
}
class example2 implements classT {
// 类型 "example2" 中缺少属性 "func1",但类型 "classT" 中需要该属性。
func2() {
return 1;
}
}
# 4.成员可见性
你可以使用 TypeScript 控制某个方法或者属性是否对类以外的代码可见。
public(默认) 可以在任何地方被获取,因为 public 是默认的可见性修饰符,所以你不需要写它,除非处于格式或者可读性的原因。
class Greeter {
public greet() {
console.log("hi!");
}
}
const g = new Greeter();
g.greet();
protected 成员仅仅对子类可见:
class Base {
protected hi() {
console.log("hi!");
}
protected name = 1;
}
const inst = new Base();
console.log(inst.name); // 属性“name”受保护,只能在类“Base”及其子类中访问。
class personal extends Base {
say() {
this.hi();
}
}
private 不允许访问的成员,即便是子类。
class Base {
private x = 0;
}
class Derived extends Base {
// 类“Derived”错误扩展基类“Base”。
// 属性“x”在类型“Base”中是私有属性,但在类型“Derived”中不是。t
x = 1;
}
"#" 强私有
class Base {
#w = 100
#r = 100
}
const base = new Base();
base.#w; // 属性 "#w" 在类 "Base" 外部不可访问,因为它具有专用标识符。
# 5.泛型类(Generic Classes)
类跟接口一样,也可以写泛型。当使用 new 实例化一个泛型类,它的类型参数的推断跟函数调用是同样的方式:
class Base<T> {
content: T;
constructor(content: T) {
this.content = content;
}
}
const boy = new Base("string");
boy.content; // Base<string>.content: string
# 6.抽象类和成员
TypeScript 中,类、方法、字段都可以是抽象的(abstract)。就好比“模子”
抽象方法或者抽象字段是不提供实现的。这些成员必须存在在一个抽象类中,这个抽象类也不能直接被实例化。
让我们看个例子:
abstract class Base {
abstract getName(): string;
printName() {
console.log("Hello, " + this.getName());
}
}
const b = new Base(); // 无法创建抽象类的实例。
我们不能使用 new 实例 Base 因为它是抽象类。我们需要写一个派生类,并且实现抽象成员。
class Derived extends Base {
getName() {
return "world";
}
}
const d = new Derived();
d.printName();
注意,如果我们忘记实现基类的抽象成员,我们会得到一个报错:
class Derived extends Base {
// 非抽象类“Derived”不会实现继承自“Base”类的抽象成员“getName”。
getAge() {
return "world";
}
}