0%

readonly

字段可以添加一个readonly的前缀修改符,会阻止在构造函数之外的赋值

1
2
3
4
5
6
7
8
9
10
11
12
class Greeter {
readonly name:string="world";
constructor(otherName?:stirng){
if(otherName!==undefined){
this.name=otherName;
}
}
err(){
this.name="not ok";
// Cannot assign to 'name' because it is a read-only property.
}
}

类构造函数

可以使用带类型注解的参数,默认值,重载等,

1
2
3
4
5
6
7
8
class Point {
// Overloads
constructor(x: number, y: string);
constructor(s: string);
constructor(xs: any, y?: any) {
// TBD
}
}

类构造函数与函数签名的区别:

  • 构造函数不能有类型参数
  • 构造函数不能返回类型注解,因为总是返回类实例类型

Getter/Setter

1
2
3
4
5
6
7
8
9
class C {
_length = 0;
get length() {
return this._length;
}
set length(value) {
this._length = value;
}
}

规则:

  • 如果get存在而set不存在,属性自动设置为readonly
  • 如果setter参数的类型没有指定,它会被推断为getter的返回类型
  • getters和setters必须有相同的成员可见性

Ts4.3后,存取器的读取和设置可以使用不同的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Thing {
_size=0;
//这里返回number类型
get size(): number {
return this._size;
}
//这里允许传入的是string|number|boolean类型
set size(value:string | number | boolean){
let num=Number(value);
if(!Number.isFinite(num)){
this._size=0;
return;
}
this._size=num;
}
}

覆写

派生类可以覆写一个基类的字段或者属性,可以使用super语法访问基类方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Base {
greet() {
console.log("Hello, world!");
}
}

class Derived extends Base {
greet(name?: string) {
if (name === undefined) {
super.greet();
} else {
console.log(`Hello, ${name.toUpperCase()}`);
}
}
}

const d = new Derived();
d.greet();
d.greet("reader");

初始化顺序:

  • 基类字段初始化
  • 基类构造函数运行
  • 派生类字段初始化
  • 派生类构造函数运行

这意味着基类构造函数只能看到它自己的name的值,因为此时派生类字段初始化还没有运行

静态类:

静态类指的是作为类的静态成员存在于某个类的内部的类

静态类之所以存在时因为这些语言强迫所有数据和函数都要在一个类内部,但这个限制在TypeScript中不存在,所以没有静态类的需要,一个只有一个单独实例的类,在JavaScript/TypeScript中,完全可以使用普通对象替代。

举个例子,我们不需要一个static class语法,因为TypeScript中一个常规对象(或者顶级函数)可以实现一样功能

1
2
3
4
5
6
7
8
9
10
11
12
// Unnecessary "static" class
class MyStaticClass {
static doSomething() {}
}

// Preferred (alternative 1)
function doSomething() {}

// Preferred (alternative 2)
const MyHelperObject = {
dosomething() {},
};

泛型类:

类和接口一样,也可以写泛型,当使用new实例化一个泛型类,它的类型参数的推断和函数调用是同样的方式

1
2
3
4
5
6
7
8
class Box<Type> {
contents:Type;
constructor(value:Type){
this.contents=value;
}
}
const b=new Box("hello");
//const b:Box<stirng>

静态成员不允许引用类型参数:

1
2
3
4
class Box<Type>{
static defaultValue:Type;
//Static members cannot reference class type parameters
}

运行时只有一个Box.defaultValue属性槽,如果设置Box.defaultValue,就会改变Box.defaultValue,这样是不好的

使用箭头函数保留上下文

a.ts

1
2
3
4
5
6
7
8
9
10
class MyClass {
name = "MyClass";
getName = () => {
return this.name;
};
}
const c = new MyClass();
const g = c.getName;
// Prints "MyClass" instead of crashing
console.log(g());

转为a.js:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var MyClass = /** @class */ (function () {
function MyClass() {
var _this = this;
this.name = "MyClass";
this.getName = function () {
return _this.name;
};
}
return MyClass;
}());
var c = new MyClass();
c.getName();
var g = c.getName;
console.log(g());

注意:

  • this的值在运行时是正确的,即使TypeScript不检查代码
  • 这会使用更多的内存,因为每一个类实例都会拷贝一遍这个函数
  • 你不能在派生类使用super.getName,因为在原型链中并没有入口可以获取基类方法

this参数

在Ts方法或函数定义中,第一个参数且名字为this有特殊含义,该参数会在编译时被抹除Ts会检查一个有this参数的函数在调用时是否有一个正确的上下文,不用像使用箭头函数一样,我们可以给方法定义添加一个this参数,静态强制方法被正确调用

1
2
3
4
5
6
7
8
9
10
class MyClass {
name="MyClass";
getName(this:MyClass){
return this.name;
}
}
const c=new MyClass();
c.getName();
const g=c.getName;
console.log(g());//参数void不能赋值给参数MyClass

注意:和箭头函数相反

  • Js调用者依然可能在没有意识到它的时候错误使用类方法
  • 每个类一个函数,而不是每个类实例一个函数
  • 基类方法定义依然可以通过super调用

this类型

在类中,有一个特殊的名为this的类型,会动态的引用当前类的类型

1
2
3
4
5
6
7
8
class Box {
contents: string = "";
set(value: string) {
// (method) Box.set(value: string): this
this.contents = value;
return this;
}
}

这里,Ts推断set的返回类型为this而不是Box

1
2
3
4
5
6
7
8
9
10
class ClearableBox extends Box {
clear() {
this.contents = "";
}
}

const a = new ClearableBox();
const b = a.set("hello");

// const b: ClearableBox

下面这个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Box {
content: string = "";
sameAs(other: this) {
return other.content === this.content;
}
}

class DerivedBox extends Box {
otherContent: string = "?";
}

const base = new Box();
const derived = new DerivedBox();
derived.sameAs(base);
// Argument of type 'Box' is not assignable to parameter of type 'DerivedBox'.
// Property 'otherContent' is missing in type 'Box' but required in type 'DerivedBox'.

other:this不同于other:Box,derived.sameAs方法值接受类置于同一个派生类的实例

基于this的类型保护:

在类和接口的方法的返回的位置,使用this is Type,当搭配使用类型收窄,目标对象的类型会被收窄为更具体的Type

例子:对一个特定字段进行懒校验,当hasValue被验证为true,会从类型中移除undefined

1
2
3
4
5
6
7
8
9
10
11
12
class Box<T>{
value?:T;
hasValue():this is {value:T}{
return this.value!==undefined;
}
}
const box=new Box();
box.value="Game";
box.value;// (property) Box<unknown>.value?: unknown
if(box.hasValue()){
box.value;// (property) value: unknown
}

总结:

构造函数中,this表示当前实例对象,在方法中,this表示当前调用方法的对象

参考:

https://ts.yayujs.com/handbook/Class.html#%E7%B1%BB%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB-relationships-between-classes

https://www.typescriptlang.org/docs/handbook/2/classes.html