简言
TypeScript 完全支持 ES2015 中引入的类关键字。
与 JavaScript 语言的其他功能一样,TypeScript 添加了类型注解和其他语法,允许您表达类与其他类型之间的关系。
class类是一个较重要的知识。
Classes
类声明
ts中类的声明和js中的高度相似。
类定义:以class关键字声明,后面加类名(大驼峰),之后是花括号包裹类体,constructor构造函数可以不定义。
typescript
class Point {
x = 0
y = 0
}
const pt = new Point()
console.log(pt.x,pt.y);
class Point2 {
x : number
y : number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
}
const pt2 = new Point2(1,1)
console.log(pt2.x,pt2.y);
就像使用 const、let 和 var 一样,类属性的初始化器将用于推断其类型,或者显示定义类型。
类属性
类属性和其他变量定义类型一样,默认可见性是public(公共的)。
若是想只读,前面需要加 readonly修饰,这样在构造函数之外无法赋值。
typescript
class Greeter {
readonly name: string = "world";
name2 = 'zsk'
constructor(otherName?: string) {
if (otherName !== undefined) {
this.name = otherName;
}
}
}
类方法
类的函数属性称为方法。方法可以使用与函数和构造函数相同的类型注解,默认可见性是public(公共的)。
除了标准的类型注解,TypeScript 没有为方法添加任何新内容。
typescript
class Point {
x = 10;
y = 10;
scale(n: number): void {
this.x *= n;
this.y *= n;
}
}
类中的方法前面可以加set和get修饰。
TypeScript 对访问器有一些特殊的推理规则:
- 如果存在 get 但没有 set,则属性自动为只读属性。
- 如果未指定设置器(setters)参数的类型,则根据获取器(getters)的返回类型进行推断。
- 获取器和设置器必须具有相同的成员可见性。
typescript
class Thing {
_size = 0;
get size(): number {
return this._size;
}
set size(value: string | number | boolean) {
let num = Number(value);
// Don't allow NaN, Infinity, etc
if (!Number.isFinite(num)) {
this._size = 0;
return;
}
this._size = num;
}
}
实现和继承
与其他具有面向对象特性的语言一样,JavaScript 中的类可以从基类继承。
实现 implements
您可以使用 implements 子句来检查类是否满足特定接口。如果类未能正确实现该接口,则会出错:
typescript
interface Pingable {
ping(): void;
}
class Sonar implements Pingable {
ping() {
console.log("ping!");
}
}
//errors
class Ball implements Pingable {
pong() {
console.log("pong!");
}
}
类也可以实现多个接口,例如类 C 实现 A、B {。
另外,implements 子句只是检查类是否可以被视为接口类型。它根本不会改变类或其方法的类型。
typescript
interface Checkable {
check(name: string): boolean;
}
class NameChecker implements Checkable {
check(s) {
// Notice no error here
return s.toLowerCase() === "ok";
}
}
继承 extends
类可以从基类扩展而来。派生类拥有基类的所有属性和方法,还可以定义其他成员。
typescript
class Animal {
move() {
console.log("Moving along!");
}
}
class Dog extends Animal {
woof(times: number) {
for (let i = 0; i < times; i++) {
console.log("woof!");
}
}
}
const d = new Dog();
// Base class method
d.move();
// Derived class method
d.woof(3);
派生类也可以覆盖基类的字段或属性。您可以使用 super. 语法访问基类方法。
TypeScript 强制规定派生类始终是其基类的子类型。
例如,下面是重写方法的合法方式:
typescript
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");
// Alias the derived instance through a base class reference
const b: Base = d;
// No problem
b.greet();
派生类必须遵守符合(覆盖)基类的的类型。
执行顺序:
- 基类字段初始化
- 基类构造函数运行
- 派生类字段初始化
- 运行派生类构造函数
类成员可见性
类属性和类函数都是类成员。
您可以使用 TypeScript 来控制某些方法或属性是否对类外的代码可见。
可见性分为以下三种:
- public ------ 类成员的默认可见性为 public。公共成员可以在任何地方被访问,由于 public 已是默认的可见性修饰符,因此您无需在类成员上写入该修饰符,但出于样式/可读性的考虑,您可能会选择这样做。
- protected ------ 受保护成员只对其所声明类的子类可见。
- private ------ 私有的,只能自己访问。private 和 protected 类似,但不允许子类访问该成员。
与 TypeScript 类型系统的其他方面一样,private 和 protected 仅在类型检查过程中执行。这意味着,JavaScript 运行时构造(如 in 或简单的属性查询)仍可访问私有或受保护成员。
private 还允许在类型检查时使用括号符号进行访问。这使得单元测试等工作更容易访问声明为私有的字段,但缺点是这些字段是软私有的,不能严格执行隐私保护(这个感觉是弊端,最好不要这样做)。
typescript
class MySafe {
private secretKey = 12345;
}
const s = new MySafe();
// OK
console.log(s["secretKey"]);
静态成员
类可能有静态成员。这些成员与类的特定实例无关。可以通过类构造函数对象本身访问它们。
typescript
class MyClass {
static x = 0;
static printX() {
console.log(MyClass.x);
}
}
console.log(MyClass.x);
MyClass.printX();
静态成员也可以使用相同的公共、受保护和私有可见性修饰符,静态成员也会被继承。
typescript
class Base {
private static x = 0;
static getGreeting() {
return "Hello world";
}
}
class Derived extends Base {
myGreeting = Derived.getGreeting();
}
name, length, 和call这些特殊的关键词不要定义为静态成员
类泛型
类和接口一样,可以是泛型的。当使用 new 实例化一个泛型类时,其类型参数的推断方式与函数调用相同。
typescript
class Box<Type> {
contents: Type;
constructor(value: Type) {
this.contents = value;
}
}
const b = new Box("hello!");
this
重要的是要记住,TypeScript 不会改变 JavaScript 的运行时行为,而 JavaScript 因其某些特殊的运行时行为而闻名。
typescript
class MyClass {
name = "MyClass";
getName() {
return this.name;
}
}
const c = new MyClass();
const obj = {
name: "obj",
getName: c.getName,
};
// Prints "obj", not "MyClass"
console.log(obj.getName());
js的this是运行时看的,谁调用this就是指的谁。
上面可以使用箭头函数让this指向类:
typescript
class MyClass {
name = "MyClass";
getName = () => {
return this.name;
};
}
const c = new MyClass();
const g = c.getName;
// Prints "MyClass" instead of crashing
console.log(g());
这样做有一定的代价:
- 该值保证在运行时是正确的,即使是没有经过 TypeScript 检查的代码也是如此。
- 这会占用更多内存,因为每个类实例都将拥有以这种方式定义的每个函数的副本
- 不能在派生类中使用 super.getName,因为在原型链中没有条目可以从基类方法中获取
或者使用this参数:
在方法或函数定义中,名为 this 的初始参数在 TypeScript 中具有特殊意义。这些参数在编译时会被删除。
TypeScript 会检查使用 this 参数调用函数的上下文是否正确。我们可以不使用箭头函数,而是在方法定义中添加 this 参数,静态地强制方法被正确调用。
typescript
class MyClass {
name = "MyClass";
getName(this: MyClass) {
return this.name;
}
}
const c = new MyClass();
// OK
c.getName();
// Error, would crash
const g = c.getName;
console.log(g());
您可以在类和接口方法的返回位置使用此 Type。当与类型缩小(如 if 语句)混合使用时,目标对象的类型将缩小为指定的类型:
typescript
class FileSystemObject {
isFile(): this is FileRep {
return this instanceof FileRep;
}
isDirectory(): this is Directory {
return this instanceof Directory;
}
isNetworked(): this is Networked & this {
return this.networked;
}
constructor(public path: string, private networked: boolean) {}
}
class FileRep extends FileSystemObject {
constructor(path: string, public content: string) {
super(path, false);
}
}
class Directory extends FileSystemObject {
children: FileSystemObject[];
}
interface Networked {
host: string;
}
const fso: FileSystemObject = new FileRep("foo/bar.txt", "foo");
if (fso.isFile()) {
fso.content;
const fso: FileRep
} else if (fso.isDirectory()) {
fso.children;
} else if (fso.isNetworked()) {
fso.host;
}
构造参数属性
TypeScript 提供了特殊的语法,可将构造函数参数转化为具有相同名称和值的类属性。这些属性称为参数属性,通过在构造函数参数前添加 public、private、protected 或 readonly 可见性修饰符来创建。生成的字段将获得这些修饰符:
typescript
class Params {
constructor(
public readonly x: number,
protected y: number,
private z: number
) {
// No body necessary
}
}
const a = new Params(1, 2, 3);
console.log(a.x);
console.log(a.z);
类表达式声明
类表达式与类声明非常相似。唯一真正的区别是类表达式不需要名称,尽管我们可以通过它们最终绑定的标识符来引用它们:
typescript
const someClass = class<Type> {
content: Type;
constructor(value: Type) {
this.content = value;
}
};
const m = new someClass("Hello, world");
类实例类型
JavaScript 类使用 new 运算符进行实例化。鉴于类本身的类型,InstanceType 实用程序类型可以模拟这种操作。
typescript
class Point {
createdAt: number;
x: number;
y: number
constructor(x: number, y: number) {
this.createdAt = Date.now()
this.x = x;
this.y = y;
}
}
type PointInstance = InstanceType<typeof Point>
function moveRight(point: PointInstance) {
point.x += 5;
}
const point = new Point(3, 4);
moveRight(point);
point.x; // => 8
abstract 修饰符
TypeScript 中的类、方法和字段可以是抽象的。
抽象方法或抽象字段是一种尚未提供实现的方法或字段。这些成员必须存在于不能直接实例化的抽象类中。
抽象类的作用是为实现所有抽象成员的子类提供基类。当一个类没有任何抽象成员时,它就被称为具体类。
我们不能用 new 来实例化 Base,因为它是抽象的。相反,我们需要创建一个派生类并实现抽象成员:
typescript
abstract class Base {
abstract getName(): string;
printName() {
console.log("Hello, " + this.getName());
}
}
// error
const b = new Base();
class Derived extends Base {
getName() {
return "world";
}
}
const d = new Derived();
d.printName();
类之间
在大多数情况下,TypeScript 中的类在结构上与其他类型相同。
例如,这两个类可以相互替代使用,因为它们完全相同:
typescript
class Point1 {
x = 0;
y = 0;
}
class Point2 {
x = 0;
y = 0;
}
// OK
const p: Point1 = new Point2();
同样,即使没有显式继承,类之间也存在子类型关系:
typescript
class Person {
name: string;
age: number;
}
class Employee {
name: string;
age: number;
salary: number;
}
// OK
const p: Person = new Employee();
空类没有成员。**在结构类型系统中,没有成员的类型通常是其他任何类型的超类型。**因此,如果你编写了一个空类(不要!),那么任何东西都可以用来代替它:
typescript
class Empty {}
function fn(x: Empty) {
// can't do anything with 'x', so I won't
}
// All OK!
fn(window);
fn({});
fn(fn);
不要这样写,没意义。
结语
结束了。