Ts 是为了在 JS 中添加编译时类型检查而创建的 类型:对数据做的一种分类,定义了能够对数据执行的操作、数据的意义,以及允许数据接受的值的集合。
- 正确性
- 不可变性
- 封装
- 可组合性
- 可读性
组合
```typescript // 利用泛型实现可组合的系统 function first(range: T[], p: (elem: T) => boolean): T | undefined { for (let elem of range) {
} }if (p(elem)) return elem;
function findFirstNegativeNumber(numbers: number[]): number | undefined { return first(numbers, n => n < 0); }
function findFirstOneCharacterString(strings: string[]): string | undefined { return first(strings, str => str.length == 1); }
<a name="BMgeb"></a>
# 实施约束
<a name="UX4tG"></a>
## 构造函数
```typescript
declare const celsiusType: unique symbol;
class Celsius {
readonly value: number;
[celsiusType]: void;
constructor(value: number) {
// if (value < -273.15) throw new Error();
if (value < -273.15) value = -273.15;
this.value = value;
}
}
工厂函数
创建另一个对象的类或函数
declare const celsiusType: unique symbol;
class Celsius {
readonly value: number;
[celsiusType]: void;
// Changing the scope of a constructor to private removes our ability to use the new keyword.
// 即外部不能使用 new Celsius,通过工厂函数就能控制最终的构造对象
private constructor(value: number) {
this.value = value;
}
static makeCelsius(value: number): Celsius | undefined {
if (value < -273.15) return undefined;
return new Celsius(value);
}
}
- 策略模式
允许在运行时从一组算法中选择某个算法,把算法与使用算法的组件解耦。
伪代码:
// 策略接口声明了某个算法各个不同版本间所共有的操作。上下文会使用该接口来
// 调用有具体策略定义的算法。
interface Strategy is
method execute(a, b)
// 具体策略会在遵循策略基础接口的情况下实现算法。该接口实现了它们在上下文
// 中的互换性。
class ConcreteStrategyAdd implements Strategy is
method execute(a, b) is
return a + b
class ConcreteStrategySubtract implements Strategy is
method execute(a, b) is
return a - b
class ConcreteStrategyMultiply implements Strategy is
method execute(a, b) is
return a * b
// 上下文定义了客户端关注的接口。
class Context is
// 上下文会维护指向某个策略对象的引用。上下文不知晓策略的具体类。上下
// 文必须通过策略接口来与所有策略进行交互。
private strategy: Strategy
// 上下文通常会通过构造函数来接收策略对象,同时还提供设置器以便在运行
// 时切换策略。
method setStrategy(Strategy strategy) is
this.strategy = strategy
// 上下文会将一些工作委派给策略对象,而不是自行实现不同版本的算法。
method executeStrategy(int a, int b) is
return strategy.execute(a, b)
// 客户端代码会选择具体策略并将其传递给上下文。客户端必须知晓策略之间的差
// 异,才能做出正确的选择。
class ExampleApplication is
method main() is
创建上下文对象。
读取第一个数。
读取最后一个数。
从用户输入中读取期望进行的行为。
if (action == addition) then
context.setStrategy(new ConcreteStrategyAdd())
if (action == subtraction) then
context.setStrategy(new ConcreteStrategySubtract())
if (action == multiplication) then
context.setStrategy(new ConcreteStrategyMultiply())
result = context.executeStrategy(First number, Second number)
打印结果。
使用接口和类实现
class Car {
// represents a car
}
// 策略模式的接口
interface IWashingStrategy {
wash(car: Car): void;
}
// 策略的具体实现
class StandardWash implements IWashingStrategy {
wash(car: Car): void {
// ...
}
}
class PremiumWash implements IWashingStrategy {
wash(car: Car): void {
//...
}
}
// 根据需要选择相应的策略
class CarWash {
service(car: Car, premium: boolean) {
let washingStrategy: IWashingStrategy;
if (premium) {
washingStrategy = new PremiumWash();
} else {
washingStrategy = new StandardWash();
}
washingStrategy.wash(car);
}
}
使用函数实现
class Car {
// represents a car
}
// 定义策略函数的类型
type WashingStrategy = (car: Car) => void;
// 策略的具体实现, 一个函数就是一个策略
function standardWash(car: Car): void {
//...
}
function premiumWash(car: Car): void {
//...
}
// 根据需要选择相应的策略
class CarWash {
service(car: Car, premium: boolean) {
let washingStrategy: WashingStrategy;
if (premium) {
washingStrategy = premiumWash;
} else {
washingStrategy = standardWash;
}
washingStrategy(car);
}
}
函数类型或签名:
函数的实参类型和返回类型决定了函数的类型。 相同的实参 + 相同的返回类型 = 相同的类型 实参集合加上返回类型也称为函数的签名。
状态模式
可以在一个对象的内部状态变化时改变其行为,使其就像改变了自身所属的类一样
有限状态机
主要思想是程序在任意时刻仅可处于几种有限的状态中。 在任何一个特定状态中, 程序的行为都不相同, 且可瞬间从一个状态切换到另一个状态。 不过, 根据当前状态, 程序可能会切换到另外一种状态, 也可能会保持当前状态不变。 这些数量有限且预先定义的状态切换规则被称为转移。
有限状态机的特征:
- 有限的状态
- 有限的事件
- 一个初始状态
- 变换器(给定当前状态 + 事件,可以得出下个状态)
- 若干个(或无)的最终状态 ```typescript // 状态机用一个枚举表示 enum TextProcessingMode { Text, Marker, Code, }
class TextProcessor { private mode: TextProcessingMode = TextProcessingMode.Text; private result: string[] = []; private codeSample: string[] = [];
processText(lines: string[]): string[] {
this.result = [];
this.mode = TextProcessingMode.Text;
for (let line of lines) {
this.processLine(line);
}
return this.result;
}
private processLine(line: string): void {
switch (this.mode) {
case TextProcessingMode.Text:
this.processTextLine(line);
break;
case TextProcessingMode.Marker:
this.processMarkerLine(line);
break;
case TextProcessingMode.Code:
this.processCodeLine(line);
break;
}
}
private processTextLine(line: string): void {
this.result.push(line);
if (line.startsWith("<!--")) {
this.mode = TextProcessingMode.Marker;
}
}
private processMarkerLine(line: string): void {
this.result.push(line);
if (line.startsWith("```ts")) {
this.result = this.result.concat(this.codeSample);
this.mode = TextProcessingMode.Code;
}
}
private processCodeLine(line: string): void {
if (line.startsWith("```")) {
this.result.push(line);
this.mode = TextProcessingMode.Text;
}
}
}
状态由函数表示的实现:
```typescript
class TextProcessor {
private result: string[] = [];
// 状态控制有函数表示
private processLine: (line: string) => void = this.processTextLine;
private codeSample: string[] = [];
processText(lines: string[]): string[] {
this.result = [];
this.processLine = this.processTextLine;
for (let line of lines) {
this.processLine(line);
}
return this.result;
}
private processTextLine(line: string): void {
this.result.push(line);
if (line.startsWith("<!--")) {
this.processLine = this.processMarkerLine;
}
}
private processMarkerLine(line: string): void {
this.result.push(line);
if (line.startsWith("```ts")) {
this.result = this.result.concat(this.codeSample);
this.processLine = this.processCodeLine;
}
}
private processCodeLine(line: string): void {
if (line.startsWith("```")) {
this.result.push(line);
this.processLine = this.processTextLine;
}
}
}
延迟计算
传递函数而不是实际的值,在需要值的时候再调用这些函数,这样可以避免高开销的计算
class Bike {}
class Car {}
function chooseMyRide(bike: Bike, car: () => Car): Bike | Car {
if (isItRaining()) {
return car();
} else {
return bike;
}
}
function makeCar(): Car {
return new Car();
}
chooseMyRide(new Bike(), makeCar);
装饰器模式
允许你通过将对象放入包含行为的特殊封装对象中来为原对象绑定新的行为
class Widget {}
interface IWidgetFactory {
makeWidget(): Widget;
}
class WidgetFactory implements IWidgetFactory {
public makeWidget(): Widget {
return new Widget();
}
}
// SingletonDecorator 封装 IWidgetFactory
// 通过单例复用同一个实例
class SingletonDecorator implements IWidgetFactory {
private factory: IWidgetFactory;
private instance: Widget | undefined = undefined;
constructor(factory: IWidgetFactory) {
this.factory = factory;
}
public makeWidget(): Widget {
if (this.instance == undefined) {
this.instance = this.factory.makeWidget();
}
return this.instance;
}
}
函数装饰器
class Widget {}
type WidgetFactory = () => Widget;
function makeWidget(): Widget {
return new Widget();
}
function singletonDecorator(factory: WidgetFactory): WidgetFactory {
let instance: Widget | undefined = undefined;
return (): Widget => {
if (instance == undefined) {
instance = factory();
}
return instance;
}
}
function use10Widgets(factory: WidgetFactory) {
for (let i = 0; i < 10; i++) {
let wiget = factory();
}
}
// 因为 singletonDecorator() 返回一个 widgetFactory,
// 所以可以将其作为实参传递给 use10Widgets()
use10Widgets(singletonDecorator(makeWidget));
子类型
unique symbol
的使用To enable treating symbols as unique literals a new type
unique symbol
is available.unique symbol
is are subtype ofsymbol
, and are produced only from callingSymbol()
orSymbol.for()
, or from explicit type annotations. The new type is only allowed onconst
declarations andreadonly static
properties, and in order to reference a specific unique symbol, you’ll have to use thetypeof
operator. Each reference to aunique symbol
implies a completely unique identity that’s tied to a given declaration.
// Works
declare const Foo: unique symbol;
// Error! 'Bar' isn't a constant.
let Bar: unique symbol = Symbol();
// Works - refers to a unique symbol, but its identity is tied to 'Foo'.
let Baz: typeof Foo = Foo;
// Also works.
class C {
static readonly StaticSymbol: unique symbol = Symbol();
}
通过在属性定义中使用 unique symbol
,使得各个类之间可以区分类型,不能互为子类型
子类型:如果在期待类型 T 的实例的任何地方,都可以安全地使用类型 S 的实例,那么称类型 S 是类型 T 的子类型
declare const NsType: unique symbol;
class Ns {
value: number;
[NsType]: void;
constructor(value: number) {
this.value = value;
}
}
declare const LbfsType: unique symbol;
class Lbfs {
value: number;
[LbfsType]: void;
constructor(value: number) {
this.value = value;
}
}
function acceptNs(momentum: Ns): void {
console.log(`Momentum:${momentum.value} Ns`);
}
// Argument of type 'Lbfs' is not assignable to parameter of type 'Ns'.
// Property '[NsType]' is missing in type 'Lbfs' but required in type 'Ns'.
acceptNs(new Lbfs(10));
顶层类型和运行时检查
顶层类型:如果我们能够把任何值赋给一个类型,就称该类型为顶层类型。
unknown
可以将任意类型的值赋值给 unknown,但 unknown 类型的值只能赋值给 unknown 或 any
let result: unknown;
let num: number = result; // 提示 ts(2322)
let anything: any = result; // 不会提示错误
使用 unknown 后,TypeScript 会对它做类型检测。如果不缩小类型(Type Narrowing),我们对 unknown 执行的任何操作都会出现如下所示错误:
let result: unknown;
result.toFixed(); // 提示 ts(2571)
let result: unknown;
if (typeof result === 'number') {
result.toFixed(); // 此处 hover result 提示类型是 number,不会提示错误
}
Ts 中的顶层类型:Object | null | undefined(被定义为 unknown)
对于unknown的情况,只有当我们确认一个值具有某个类型(如User)时,才能把该值用作该类型。对于any的情况,我们可以立即把该值用作其他任何类型的值。any会绕过类型检查。
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
function deserialize(input: string): unknown {
return JSON.parse(input);
}
function greet(user: User): void {
console.log(`hi ${user.name}`);
}
function isUser(user: any): user is User {
if (user === null || user === undefined) {
return false;
}
return typeof user.name === 'string';
}
let user: unknown = deserialize('{"name": "Alice"}');
if (isUser(user)) {
greet(user);
}
user = deserialize("null");
// Argument of type 'unknown' is not assignable to parameter of type 'User'.ts(2345)
// 从可以就可以看出使用 unknown 可以避免绕过类型检查
greet(user);
底层类型
如果一个类型是其他类型的子类型,就称之为底层类型
Ts 中 never 是底层类型,所以能把它赋值给其他任何类型
enum TurnDirection {
Left,
Right
}
function turnAngle(turn: TurnDirection): number {
switch (turn) {
case TurnDirection.Left: return -90;
case TurnDirection.Right: return 90;
default: return fail("Unknow turnDirection");
}
}
function fail(message: string): never {
console.log(message);
throw new Error(message);
}
面向对象
面向对象编程(Object-Oriented Programming, OOP)
OOP 是基于对象的概念的一种编程范式,对象可以包含数据和代码。 数据是对象的状态,代码是一个或多个方法,也叫做“消息”。 在面向对象系统中,通过使用其他对象的方法,对象之间可以“对话”或者发送消息。
class Point { y: number; x: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
}
class Circle extends Shape { // 成为类型的组成部分 center: Point; radius: number;
constructor(id: string, center: Point, radius: number) {
super(id);
this.center = center;
this.radius = radius;
}
}
<a name="5qQ7q"></a>
# 泛型数据结构
> 泛型类型是指参数化一个或多个类型的泛型函数、类、接口等。泛型类型允许我们编写能够使用不同类型的通用代码,从而实现高度的代码重用。
<a name="3vo7j"></a>
## 泛型约束
泛型使得我们能够使用无数的类型,为了控制类型的范围,则出现了泛型约束的概念。
```typescript
interface LengthDefine {
length: number;
}
//这样函数参数就必须拥有 length 的属性
student = <T extends LengthDefine>(value: T): T => {
console.log(value.length);
return value;
}