简言
TypeScript 完全支持 ES2015 中引入的类关键字。
与 JavaScript 语言的其他功能一样,TypeScript 添加了类型注解和其他语法,允许您表达类与其他类型之间的关系。
class类是一个较重要的知识。
Classes
类声明
ts中类的声明和js中的高度相似。
类定义:以class关键字声明,后面加类名(大驼峰),之后是花括号包裹类体,constructor构造函数可以不定义。
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修饰,这样在构造函数之外无法赋值。
class Greeter { readonly name: string = "world"; name2 = 'zsk' constructor(otherName?: string) { if (otherName !== undefined) { this.name = otherName; } } }
类方法
类的函数属性称为方法。方法可以使用与函数和构造函数相同的类型注解,默认可见性是public(公共的)。
除了标准的类型注解,TypeScript 没有为方法添加任何新内容。
class Point { x = 10; y = 10; scale(n: number): void { this.x *= n; this.y *= n; } }
类中的方法前面可以加set和get修饰。
TypeScript 对访问器有一些特殊的推理规则:
- 如果存在 get 但没有 set,则属性自动为只读属性。
- 如果未指定设置器(setters)参数的类型,则根据获取器(getters)的返回类型进行推断。
- 获取器和设置器必须具有相同的成员可见性。
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 子句来检查类是否满足特定接口。如果类未能正确实现该接口,则会出错:
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 子句只是检查类是否可以被视为接口类型。它根本不会改变类或其方法的类型。
interface Checkable { check(name: string): boolean; } class NameChecker implements Checkable { check(s) { // Notice no error here return s.toLowerCase() === "ok"; } }
继承 extends
类可以从基类扩展而来。派生类拥有基类的所有属性和方法,还可以定义其他成员。
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 强制规定派生类始终是其基类的子类型。
例如,下面是重写方法的合法方式:
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 还允许在类型检查时使用括号符号进行访问。这使得单元测试等工作更容易访问声明为私有的字段,但缺点是这些字段是软私有的,不能严格执行隐私保护(这个感觉是弊端,最好不要这样做)。
class MySafe { private secretKey = 12345; } const s = new MySafe(); // OK console.log(s["secretKey"]);
静态成员
类可能有静态成员。这些成员与类的特定实例无关。可以通过类构造函数对象本身访问它们。
class MyClass { static x = 0; static printX() { console.log(MyClass.x); } } console.log(MyClass.x); MyClass.printX();
静态成员也可以使用相同的公共、受保护和私有可见性修饰符,静态成员也会被继承。
class Base { private static x = 0; static getGreeting() { return "Hello world"; } } class Derived extends Base { myGreeting = Derived.getGreeting(); }
name, length, 和call这些特殊的关键词不要定义为静态成员
类泛型
类和接口一样,可以是泛型的。当使用 new 实例化一个泛型类时,其类型参数的推断方式与函数调用相同。
class Box
{ contents: Type; constructor(value: Type) { this.contents = value; } } const b = new Box("hello!"); this
重要的是要记住,TypeScript 不会改变 JavaScript 的运行时行为,而 JavaScript 因其某些特殊的运行时行为而闻名。
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指向类:
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 参数,静态地强制方法被正确调用。
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 语句)混合使用时,目标对象的类型将缩小为指定的类型:
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 可见性修饰符来创建。生成的字段将获得这些修饰符:
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);
类表达式声明
类表达式与类声明非常相似。唯一真正的区别是类表达式不需要名称,尽管我们可以通过它们最终绑定的标识符来引用它们:
const someClass = class
{ content: Type; constructor(value: Type) { this.content = value; } }; const m = new someClass("Hello, world"); 类实例类型
JavaScript 类使用 new 运算符进行实例化。鉴于类本身的类型,InstanceType 实用程序类型可以模拟这种操作。
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
function moveRight(point: PointInstance) { point.x += 5; } const point = new Point(3, 4); moveRight(point); point.x; // => 8 abstract 修饰符
TypeScript 中的类、方法和字段可以是抽象的。
抽象方法或抽象字段是一种尚未提供实现的方法或字段。这些成员必须存在于不能直接实例化的抽象类中。
抽象类的作用是为实现所有抽象成员的子类提供基类。当一个类没有任何抽象成员时,它就被称为具体类。
我们不能用 new 来实例化 Base,因为它是抽象的。相反,我们需要创建一个派生类并实现抽象成员:
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 中的类在结构上与其他类型相同。
例如,这两个类可以相互替代使用,因为它们完全相同:
class Point1 { x = 0; y = 0; } class Point2 { x = 0; y = 0; } // OK const p: Point1 = new Point2();
同样,即使没有显式继承,类之间也存在子类型关系:
class Person { name: string; age: number; } class Employee { name: string; age: number; salary: number; } // OK const p: Person = new Employee();
空类没有成员。**在结构类型系统中,没有成员的类型通常是其他任何类型的超类型。**因此,如果你编写了一个空类(不要!),那么任何东西都可以用来代替它:
class Empty {} function fn(x: Empty) { // can't do anything with 'x', so I won't } // All OK! fn(window); fn({}); fn(fn);
不要这样写,没意义。
结语
结束了。
还没有评论,来说两句吧...