[TOC] **编程思想:** 面向对象、面向过程、函数式编程 **面向对象是一种编程思想。(切入点)** **面向对象:** Oriented (基于) Object (事物) , 简称:OO 是一种编程思想,它提出一切以对象对切入点思考问题。 - JS没有类型检查,如果使用面向对象的方式开发,会产生大量的接口,这会导致调佣复杂度剧增。 所以必须通过严格的类型检查来避免错误。尽管可以使用注解、文档甚至是记忆力,但是它们没有强约束力。 - TS带来了完整的类型系统。在开发复杂项目时,都可以获得完整的类型检查。并且这种检查是据有强约束力的。 对象继承规则:子类成员不能改变父类成员的属性类型 **设计模式:** 面对一些常见的功能场景,有一套固定、经过多年实践、且非常成熟的方式处理这些问题,这种方法被称之为设计模式。 ## :-: 抽象类 ``` // 模版模式:有些方法所有的子类实现的流程完全一致,只是流程中的某个步骤(规则)具体实现不一致。可以将该方法提取到父类,在父类中完成整个流程的实现。 // abstract -- 抽象类(对象不能单独创建,只能被继承) // 抽象类:只表示一个抽象概念,主要用于提取子类共有的成员,而不能直接创建它的对象。 abstract class A { // readonly -- 被修饰的字段为只读类型,不可修改 // public -- 公开的(默认),所有代码均可访问。 // protected -- 受保护的成员,只能在自身和子类中访问。 // private -- 私有的,只能在自身访问,继承的子类无法访问。 protected a: number = 1; x: number = 0; y: number = 0; // abstract -- 抽象成员,必须出现在抽象类中。这些抽象成员必须在子类中实现。 // 父级中,可能知道有些成员是必须存在的,但是不知道该成员的值或实现是什么。因此需要强约束,让继承该类的子类必须实现这个成员(属性/方法)。 abstract name: string; // 定义抽象成员 (子类必须实现) abstract rule(targetX: number, targetY: number): boolean; // 定义抽象方法 (子类必须实现这个方法) // 模版模式:有些方法所有的子类实现的流程完全一致,只是流程中的某个步骤(规则)具体实现不一致。 // 可以将该方法提取到父类,在父类中完成整个流程的实现。遇到实现不一致的方法时,将该方法做成抽象方法。 move(targetX: number, targetY: number): boolean { console.log("1.边界判断"); console.log("2.目标位置是否有乙方棋子"); // - 3.棋子移动规则判断 if (this.rule(targetX, targetY)) { this.x=targetX; this.y=targetY; return true; } return false; } } class B extends A { // 假设rule是象棋的规则函数 rule(targetX: number, targetY: number): boolean { console.log("- 3.棋子移动规则判断"); return true; } name = "马"; // 父级定义了'抽象成员',子级必须要处理它,给予该属性赋值。 e?: string = null; // super -- 读取父类资源,调用父类的属性/方法 // super.b } new B().move(5, 5); // const a = new A(); // 已经定义了抽象类,将无法创建抽象类的实例。 const b: A = new B(); // 鸭子辩型 if (b instanceof B) { // 触发类型保护 b.e = undefined; } ``` ``` export interface IFireShow { name: string; } // 抽象类 export abstract class Animal {} // 子类 (必须实现IFireShow的接口) export class Lion extends Animal implements IFireShow { name = "xxx"; } // 类型保护函数:通过调用该函数,会触发TS的类型保护,该函数必须返回 Boolean function hasShow(ani: object): ani is IFireShow { if ((ani as IFireShow).name) { return true; } return false; } // 接口可以继承自类(class) // interface C extends Aobj,Bobj { } ``` ## :-: 静态成员 ``` // 实例成员:属于某个类的对象。'new User().log()' // 静态成员(static):属于某个类。'User.login()' class User { static Users: User[] = []; constructor(public name: string, public pwd: string) { User.Users.push(this); } // 定义静态方法 static login(name: string, pwd: string): User | undefined { return User.Users.find(u => name === u.name && pwd === u.pwd); } log() { console.log(this.name, this.pwd); } } const u1 = new User("name_111", "pwd_111"); const u2 = new User("name_222", "pwd_222"); console.log(User.Users); console.log(User.login("name_111", "pwd_111")); ``` ## :-: 设计模式-单例模式 ``` // 所谓单例,就是整个程序有且仅有一个实例。该类负责创建自己的对象,同时确保只有一个对象被创建。 class Board { width: number = 500; height: number = 700; init() { console.log("初始化棋盘"); } private constructor() {} // 让外部无法通过new的方式创建 private static _board?: Board; static createBoard(): Board { return this._board ? this._board : (this._board = new Board()); } } // new Board(); 此时构造函数是私有的,外部无法创建对象 Board.createBoard(); // 只会创建一次,无论被调用多少次都返回一样的结果。 ``` ## :-: 索引器 ``` // 可以开启'noImplicitAny'配置对隐式any进行检查 class User1 { [prop: string]: string | boolean; // 对所有成员进行类型限制 name = "xxx"; } new User1()[0] = true; ``` ## :-: this指向约束 ``` // 使用bind、apply、call可以手动绑定this对象。 // 配置'noImplicitThis'为true,表示不允许this隐式的指向any interface u { name:string; age:number; sayHello(this:u): void; // 手动声明该函数中this的指向。 } const user2:u = { name:"xxx", age:18, // sayHello(this:u) { ··· } sayHello() { console.log(this.name, this.age); } }; user2.sayHello(); // const fun=user2.sayHello;fun(); // 已经声明约束了this指向,这种方式调用会报错 ``` ## :-: 装饰器 ### 概述 > - 面向对象的概念(java:注解、C#:特征),decorator > - 在`Angular`中大量使用,`React`中也有用到。 > - 目前 js 支持装饰器,但是还没有正式成为标准。(目前处于建议征集的第二阶段) ### 解决的问题 > - 装饰器,分离关注点。 > - 装饰器的本质是一个函数,用于修饰 类、属性、参数 ***** > `Object.getPrototypeOf(Obj).constructor.name` > 得到一个对象的原型 --> 构造函数 --> 定义的名 (User) ***** ### :-: 类装饰器 ```ts // function test(target: Function) {} // 在ts中约束一个构造函数 Function | new()=>object function test(target: new (...args: any[]) => object) { // target -- 是类(A) new A(); } @test class A { ··· } ``` ### :-: 成员装饰器 ```ts // 属性 -- 属性装饰器也是一个函数,该函数提供两个参数 // 1.如果是静态属性,则为类本身。如果是实例属性,则为类的原型 // 2.固定为属性名 // 方法 -- 方法装饰器也是一个函数,该函数提供三个参数 // 1.如果是静态属性,则为类本身。如果是实例方法,则为类的原型 // 2.固定为属性名 // 3.属性描述对象 function enumerable(target: any, key: string, descriptor: PropertyDescriptor) { descriptor.enumerable = true; // 让方法也可以被枚举 } class B { @enumerable xxxxx() { /* ··· */ } } ``` ### :-: 推荐库 - reflect-metadata -- 它专门用于维护类、属性的元数据 ```ts import "reflect-metadata"; // 库是全局的只需导入一次,不返回任何变量 const key = Symbol.for("descriptor"); // 绝对唯一 export function descriptor(description: string) { return Reflect.metadata(key, description); } export function printObj(obj: any) { const cons = Object.getPrototypeOf(obj); if (Reflect.hasMetadata(key, cons)) { console.log(Reflect.getMetadata(key, cons)); } else { console.log(cons.constructor.name); } // 输出所有的属性描述和属性值 for (const k in obj) { if (Reflect.hasMetadata(key, obj, k)) { console.log(`\t${Reflect.getMetadata(key, obj, k)} : ${obj[k]}`); } else { console.log(`\t${k} : ${obj[k]}`); } } } // 第三方库:reflect-metadata -- 它专门用于维护类、属性的元数据 import { descriptor, printObj } from "./Descriptor"; @descriptor("文章") class Article { @descriptor("标题") title = "这是文章标题"; @descriptor("内容") content = "这是文章的内容"; @descriptor("时间") date = new Date(); } const article = new Article(); printObj(article); /* * Article * 标题 : 这是文章标题 * 内容 : 这是文章的内容 * 时间 : Sat Mar 28 2020 14:53:55 GMT+0800 (GMT+08:00) */ ``` - class-validator -- 属性装饰器,数据验证 ```ts import { IsNotEmpty, validate, MinLength, MaxLength, Max, Min } from "class-validator"; import { Type } from "class-transformer"; class RegUser { @IsNotEmpty({ message: "帐号不能为空!" }) // 非空校验 loginId!: string; @IsNotEmpty({ message: "密码不能为空!" }) @MinLength(6, { message: "密码不能小于6个字符!" }) @MaxLength(16, { message: "密码不能大于16个字符!" }) loginPwd!: string; @Max(100, { message: "年龄必须小于100" }) @Min(0, { message: "年龄必须大于0" }) @Type(() => Number) // 一般用于将`axios`请求过来的数据中为string类型的字段,转换为number类型。"123" -> 123 age!: number; gender!: "男" | "女"; } const post = new RegUser(); post.loginId = "id_xxx"; // post.loginPwd = "pwd_xxx"; post.age = -10; validate(post).then(errors => { // 验证方法是异步执行的 console.log(errors); /* * [ * ValidationError { * target: RegUser { loginId: 'id_xxx', age: -10 }, -- 验证哪个实例 * value: undefined, -- 赋的值是什么 * property: 'loginPwd', -- 属性名 * children: [], * constraints: { -- 没有实现的约束 * maxLength: '密码不能大于16个字符!', * minLength: '密码不能小于6个字符!', * isNotEmpty: '密码不能为空!' * } * }, * ValidationError { * target: RegUser { loginId: 'id_xxx', age: -10 }, * value: -10, * property: 'age', * children: [], * constraints: { min: '年龄必须大于0' } * } * ] */ }); ``` - class-transformer -- 将平面对象转换成类的对象。 ### 补充 - 参数装饰器(依赖注入、依赖倒置) - 关于 ts 自动注入 - 面向对象 `AOP` ## :-: 类型演算 - `typeof` -- 当作用于类的时候,得到的类型,是该类的构造函数。 - `keyof` -- 作用于 类、接口、类型别名,用于获取其他类型中的所有成员名组成的联合类型。`const abc:keyof User;` - `in` -- 该关键字往往和 keyof 联用,用于限制某个索引的取值范围。 - `type UserString = { [key in 'loginId' | 'loginPwd' | 'age']:string }` - `type UserString = { [key in keyof User]:string }` - `type UserReadonly = { readonly [key in keyof User]?:User[key] }` - `type Partial<T> = { readonly [key in keyof T]?:T[key] }` -> `const u:Partial<User> = { ··· }` ### TS自带的类型演算 ```ts Required<T> -- 将类型T中的成员变为必须 Readonly<T> -- 将类型T中的成员变为只读 Exclude<T,U> -- 从T中移除可以赋值给U的类型 Extract<T,U> -- 相反,提取T中可以赋值给U的类型 NonNullable<T> -- 从T中移除 null 和 undefined ReturnType<T> -- 获取函数返回值的类型 InstanceType<T> -- 获取构造函数类型的实例类型 ``` ## :-: 声明文件 ### 概述 - 创建一个以`.d.ts`结尾的文件 - 其作用是为普通的 js 代码文件提供类型声明 - 声明文件的位置 - 放置到 `tsconfig.json` 配置中包含的目录中 - 放置到 `node_modules/@types` 文件夹中 - 手动配置 - 与 js 代码所在的目录相同,并且文件名也相同的文件,用 ts 代码书写的工程发布之后的格式,(推荐) ### 编写 - 手动编写 - 自动生成 -- 工程是使用 ts 开发的,配置`declaration`为`true`即可。