一、ts简介
- TypeScript 是由微软开发的一款开源的编程语言
- TypeScript 是javascript的超级,遵循最新的ES6,ES5规范,TypeScript扩展了javascript的语法
- TypeScript 更像后端java,C#这样的面向对象语言,以让js开发大型企业项目
- 谷歌也在大力支持TypeScript的推广,谷歌的angular2.x起就是基于TypeScript语法
- 最新的vue,react也可集成TypeScript
二、安装
1. 全局安装
sudo npm install -g typescript
2. 查看版本及编译
#查看版本
tsc --v
Version 3.8.3
#编译
tsc index.ts // 编译这个文件会在index.ts的同级目录生产一个index.js文件
3. 配置vscode 自动编译ts
# 第一步:创建tsconfig.json配置文件
# tsc --init 生成配置文件
# tsconfig.json
{
"compilerOptions": {
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
# 将编译后的ts输出到指定目录
"outDir": "./js", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
# 第二步:vscode -> 任务 -> 运行任务 -> 监视tsconfig.json
三、数据类型
- typescript中为了使编码更加规范,更利于维护,增加了类型校验,在typescript中主要给我们提供了一下几种数据类型
- 写ts代码必须指定类型
# 任意类型 any 声明为 any 的变量可以赋予任意类型的值。
# 数字类型 number 双精度 64 位浮点值。它可以用来表示整数和分数。
# 字符串类型 string 一个字符系列,使用单引号(')或双引号(")来表示字符串类型。反引号(`)来定义多行文本和内嵌表达式
# 布尔类型 boolean 、表示逻辑值:true 和 false。
# 数组类型 array 声明变量为数组。
// 在元素类型后面加上[]
let arr: number[] = [1, 2];
// 或者使用数组泛型
let arr: Array<number> = [1, 2];
# 元组 无 元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同。
let x: [string, number];
x = ['Runoob', 1]; // 运行正常
x = [1, 'Runoob']; // 报错
console.log(x[0]); // 输出 Runoob
# 枚举 enum 枚举类型用于定义数值集合。
# void void 用于标识方法返回值的类型,表示该方法没有返回值。
# null null 表示对象值缺失。
# undefined undefined 用于初始化变量为一个未定义的值
# never never never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。
1. 数组 array
ts中定义数组有三种方式
第一种定义数组
// 指定数组里边的元素都应该是 number类型 let arr:number[] = [11,22,33]
第二种定义数组
// 表达定义一个array类型 里边的每一项都是number类型 let arr:Array<number> = [11,22,33]
第三种定义数组
let arr:any[] = [11,'22',33]
2. 元组 tuple
- 元组属于数组的一种,元组类型用来表示已知元素数量和类型的数组,各元素的类型不必相同,对应位置的类型需要相同。
let arr:[number,string] = [11,'sss']
// 元组越界问题
let aaa: [string, number] = ['aaa', 5];
// 添加时不会报错
aaa.push(6);
// 打印整个元祖不会报错
console.log(aaa); // ['aaa',5,6];
// 打印添加的元素时会报错
console.log(aaa[2]); // error
3. 枚举 enum
- 是只读属性,无法修改
- 枚举成员值默认从 0 开始递增,可以自定义设置初始值
- 如果表示符没有赋值 它的值就是索引下标
# 没有赋值就默认下标
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
console.log('c',c) // 1
# 赋值后面的值就是前一个下标加一
enum Color {Red, Green=4, Blue}
let c: Color = Color.Green;
let b: Color = Color.Blue
console.log('c',c) // 4
console.log('b', b) // 5
字符串枚举
enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
console.log(Direction['Right'], Direction.Up); // Right Up
// 如果设定了一个变量为字符串之后,后续的字段也需要赋值字符串,否则报错:
// 枚举成员必须具有初始化表达式
enum Direction {
Up = 'UP',
Down, // error TS1061: Enum member must have initializer
Left, // error TS1061: Enum member must have initializer
Right // error TS1061: Enum member must have initializer
}
// 异构枚举
// 即将数字枚举和字符串枚举结合起来混合起来使用,如下
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
常量枚举与普通枚举的区别
- 常量枚举会在编译阶段被删除
- 枚举成员只能是常量成员
- 常量枚举不能包含计算成员,如果包含了计算成员,则会在编译阶段报错 ```javascript const enum Colors { Red, Yellow, Blue }
// 常量枚举会在编译阶段被删除 let myColors = [Colors.Red, Colors.Yellow, Colors.Blue];
// 编译成 JS “use strict”; var myColors = [0 / Red /, 1 / Yellow /, 2 / Blue /];
<a name="gDYP1"></a>
##### 枚举的使用场景
```javascript
// 字段使用 0 - 6 标记对应的日期,这时候就可以使用枚举可提高代码可读性
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true
以下代码存在的问题:
可读性差:很难记住数字的含义
可维护性差:硬编码,后续修改的话牵一发动全身
function initByRole(role) {
if (role === 1 || role == 2) {
console.log("1,2")
} else if (role == 3 || role == 4) {
console.log('3,4')
} else if (role === 5) {
console.log('5')
} else {
console.log('')
}
}
# 使用枚举后
enum Role {
Reporter,
Developer,
Maintainer,
Owner,
Guest
}
function init(role: number) {
switch (role) {
case Role.Reporter:
console.log("Reporter:1");
break;
case Role.Developer:
console.log("Developer:2");
break;
case Role.Maintainer:
console.log("Maintainer:3");
break;
case Role.Owner:
console.log("Owner:4");
break;
default:
console.log("Guest:5");
break;
}
}
init(Role.Developer);
4. 任意 any
- 声明为 any 的变量可以赋予任意类型的值。
let d:any = 123
d = '123'
d = ['23']
# 任意类型的用处
let Box:any = document.getElementById('box')
Box.style.color = 'red'
5. null 和 andefined
- null 和 andefined 其他 (never 类型)数据类型的子类型 ```javascript let num:number | undefined; console.log(num) // undefined
let num:number | undefined; num = 123 console.log(num) // 123
let num:number | null | undefined num = 12 console.log(num) // 12
<a name="lfuGu"></a>
#### 6. void类型
- typescript中的void表示没有任何类型,一般用于定义方法的时候没有返回值
- void 表示没有任何类型(可以被赋值为 null 和 undefined)。
- 拥有 void 返回值类型的函数能正常运行。拥有 never 返回值类型的函数无法正常返回,无法终止,或会抛出异常。
```javascript
// 用于标识方法返回值的类型,表示该方法没有返回值。
function hello():void{
console.log('void')
}
hello()
// 如果方法有返回值 就要指定返回值类型
function hello():number{
return 123
}
console.log(hello())
7. never类型
- never 是其它类型(包括 null 和 undefined)的子类型,代表从不会出现的值。这意味着声明为 never 类型的变量只能被 never 类型所赋值,在函数中它通常表现为抛出异常或无法执行到终止点(例如无限循环),示例代码如下:
- never 表示一个不包含值的类型,即表示永远不存在的值。
let x: never;
let y: number;
// 运行错误,数字类型不能转为 never 类型
x = 123;
// 运行正确,never 类型可以赋值给 never类型
x = (()=>{ throw new Error('exception')})();
// 运行正确,never 类型可以赋值给 数字类型
y = (()=>{ throw new Error('exception')})();
// 返回值为 never 的函数可以是抛出异常的情况
function error(message: string): never {
throw new Error(message);
}
// 返回值为 never 的函数可以是无法被执行到的终止点的情况
function loop(): never {
while (true) {}
}
8. 类型断言
- 有时候你会遇到这样的情况,你会比TypeScript更了解某个值的详细信息。 通常这会发生在你清楚地知道一个实体具有比它现有类型更确切的类型
- 通过类型断言这种方式可以告诉编译器,“相信我,我知道自己在干什么”。 类型断言好比其它语言里的类型转换,但是不进行特殊的数据检查和解构。 它没有运行时的影响,只是在编译阶段起作用。 TypeScript会假设你,程序员,已经进行了必须的检查。
- 两种形式是等价的。 至于使用哪个大多数情况下是凭个人喜好;然而,当你在TypeScript里使用JSX时,只有 as语法断言是被允许的。
- 该语法已经过时,并且与 React JSX 代码(在 .tsx 文件中)不兼容。
// 类型断言有两种形式。 其一是“尖括号”语法:
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// 另一个为as语法: jsx里边只支持as语法
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
9. unknown 类型
- TypeScript 3.0 引入了新的unknown 类型,它是 any 类型对应的安全类型。
unknown 和 any 的主要区别是 unknown 类型会更加严格。
在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的 顶级类型 (也被称作 全局超级类型)。
- 就像所有类型都可以被归为 any,所有类型也都可以被归为 unknown。这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型(另一种是 any) ```javascript // 1.any let value: any;
value = true; // OK value = 42; // OK value = “Hello World”; // OK value = []; // OK value = {}; // OK value = Math.random; // OK value = null; // OK value = undefined; // OK value = new TypeError(); // OK value = Symbol(“type”); // OK
// 这许多场景下,这样的机制都太宽松了。使用any类型会破坏类型安全。 let s: string = value // OK
// 2. unknown let value: unknown;
value = true; // OK value = 42; // OK value = “Hello World”; // OK value = []; // OK value = {}; // OK value = Math.random; // OK value = null; // OK value = undefined; // OK value = new TypeError(); // OK value = Symbol(“type”); // OK
//类型保护机制 let s:string = value // Error 不能将类型“unknown”分配给类型“string”
<a name="3XmR8"></a>
### 四、ts中的函数
- 函数的定义
- 可选参数
- 默认参数
- 剩余参数
- 函数重载
- 箭头函数
<a name="bJPfT"></a>
#### 1. 函数声明法
```javascript
// 声明返回值类型,声明string类型要是返回不是string类型就会报错
function run():string{
return 'run'
}
let run2 = function():number{
return 123
}
# ts中定义方法传参
// 定义每个传递参数的数据类型
function getInfo(name:string,age:number):string{
return name+age
}
console.log(getInfo('sss',222)) // sss222
// 没有返回值的函数
function run3():void{
}
2. 可选参数
- es5里面方法的实参和行参可以不一样,但是在ts中必须一样,如果不一样就需要配置可选参数
- 可选参数必须配置到行参的最后边
# 配置可选参数 就是在age后边加一个 ? 代表可传可不传
function getInfo(name:string,age?:number):string{
if(age){
return name+age
}else{
return name
}
}
console.log(getInfo('sss'))
console.log(getInfo('sss',111))
// 错误写法 可选参数必须配置到行参的最后边
function getInfo(name?:string,age:number):string{
if(age){
return name+age
}else{
return name
}
}
console.log(getInfo('sss',111))
3. 默认参数
- es5里面没法设置默认参数, es6和ts中都可以设置默认参数
// 不传递参数 age参数 默认20
function getInfo(name:string,age:number=20):string{
if(age){
return `${name}----${age}`
}else{
return name
}
}
console.log(getInfo('sss')) // 20
console.log(getInfo('sss',30)) // 30
4. 剩余参数
// 同理和原声的剩余参数写法一样,在ts中就是加了类型
function sun(a:number, ...result:number[]):number{
let num:number = a
for(let i = 0; i < result.length; i++){
num += result[i]
}
return num
}
console.log(sun(1,2,3,4,5))
5. 函数重载
- 重载指的是两个或者两个以上同名函数,但是他们的参数不一样,这时会出现函数重载的情况
- TypeScript函数重载是指为同一个函数提供多个函数类型,它的意义在于让你清晰的知道传入不同的参数得到不同的结果。如果不是这种情况,那就不需要使用函数重载
- 重载只是 TypeScript 编译时的。 ```javascript // 实现一个需求输入string类型 返回string类型 输入number返回number
function getInfo(value: string):string function getInfo(value: number):number
function getInfo(value: string | number): string | number { if (typeof value === ‘string’) { return ‘name’ } else { return 18 } }
const info = getInfo(‘name’)
of
//泛型重载
function getInfo
function getInfo(value: string | number): string | number { if (typeof value === ‘string’) { return ‘name’ } else { return 18 } } const info = getInfo(‘name’)
<br />
<a name="IbAyM"></a>
### 五、ts中的类
<a name="xXf2w"></a>
#### 1. 类的概念
- 虽然 JavaScript 中有类的概念,但是可能大多数 JavaScript 程序员并不是非常熟悉类,这里对类相关的概念做一个简单的介绍。
- 类(Class):定义了一件事物的抽象特点,包含它的属性和方法
- 对象(Object):类的实例,通过 `new` 生成
- 面向对象(OOP)的三大特性:封装、继承、多态
- 封装(Encapsulation):将对数据的操作细节隐藏起来,只暴露对外的接口。外界调用端不需要(也不可能)知道细节,就能通过对外提供的接口来访问该对象,同时也保证了外界无法任意更改对象内部的数据
- 继承(Inheritance):子类继承父类,子类除了拥有父类的所有特性外,还有一些更具体的特性
- 多态(Polymorphism):由继承而产生了相关的不同的类,对同一个方法可以有不同的响应。比如 `Cat` 和 `Dog` 都继承自 `Animal`,但是分别实现了自己的 `eat` 方法。此时针对某一个实例,我们无需了解它是 `Cat` 还是 `Dog`,就可以直接调用 `eat` 方法,程序会自动判断出来应该如何执行 `eat`
- 存取器(getter & setter):用以改变属性的读取和赋值行为
- 修饰符(Modifiers):修饰符是一些关键字,用于限定成员或类型的性质。比如 `public` 表示公有属性或方法
- 抽象类(Abstract Class):抽象类是供其他类继承的基类,抽象类不允许被实例化。抽象类中的抽象方法必须在子类中被实现
- 接口(Interfaces):不同类之间公有的属性或方法,可以抽象成一个接口。接口可以被类实现(implements)。一个类只能继承自另一个类,但是可以实现多个接口
<a name="QyFl4"></a>
#### 2. 定义类
```javascript
class Person{
name:string; // 属性 前面省略了 pulbic关键词
constructor(name:string){ // 构造函数 实例化类时候触发的
this.name = name
}
getName():string{
return this.name
}
setName(name:string):void{
this.name = name
}
}
let p = new Person('张三')
console.log(p.getName()) // 张三
p.setName('李四')
console.log(p.getName()) // 李四
3. 继承
- 使用 extends 关键字实现继承,子类中使用 super 关键字来调用父类的构造函数和方法。
class Person {
name: string;
constructor(name: string) { // 构造函数 实例化类时候触发的
this.name = name
}
run():string{
return `${this.name}在运动`
}
}
// let p = new Person('王')
// console.log(p.run())
class Web extends Person{
constructor(name:string){
super(name) // super表示调用父类的构造函数
}
}
let w = new Web('李四')
console.log(w.run()) // 李四在运动
4. 存取器
- 使用 getter 和 setter 可以改变属性的赋值和读取行为:
```javascript
class Animal {
private _name: string
constructor(name: string) {
} get name() {this._name = name;
} set name(value) {return this._name;
} }this._name = value
let a = new Animal(‘张三’); a.name = ‘李四’; console.log(a.name); // 李四Ï
<a name="fadJd"></a>
#### 5. 多态
- 多态:父类定义一个方法不去实现,让继承他的子类去实现,每一个子类有不同的表现
```javascript
class Base {
public age: number;
constructor(age: number) {
this.age = age
}
counts(): void {
console.log(this.age)
}
}
class children1 extends Base {
constructor(age: number) {
super(age)
}
counts(): void { /* 多态,重写方法不执行父类方法 */
console.log(this.age - 1)
}
}
class children2 extends Base {
constructor(age: number) {
super(age)
}
counts(): void {
console.log(this.age + 1)
}
}
const chi1 = new children1(18)
const chi2 = new children2(19)
console.log(chi1.age,chi2.age) // 18 19ÏÏ
六、类里边修饰符
ts类里面修饰符定义属性的时候 给我们提供了 三种修饰符
public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
1. public 公有
class Person {
public name: string; // 公有属性
constructor(name: string) { // 构造函数 实例化类时候触发的
this.name = name
}
run(): string {
return `${this.name}在运动`
}
}
class Web extends Person{
constructor(name:string){
super(name) // super表示调用父类的构造函数
}
}
# 在子类里边可以访问
let w = new Web('李四')
console.log(w.run()) // 李四在运动
# 类外部访问公有属性 实例爷可以访问
let p = new Person('哈哈')
console.log(p.name) // 哈哈
2. protected 保护类型
- protected定义的属性可以在类内部或子类拿到 类外部拿不到
当构造函数修饰为 protected 时,该类只允许被继承: ```javascript
protected定义的属性可以在类内部或子类拿到 类外部拿不到
class Person { protected name: string; // 保护类型
constructor(name: string) { // 构造函数 实例化类时候触发的
this.name = name
}
run(): string {
return `${this.name}在运动`
} }
class Web extends Person{ constructor(name:string){ super(name) // super表示调用父类的构造函数 }
work(){
console.log(`${this.name}`) // 李四
}
}
let w = new Web(‘李四’)
console.log(w.run()) // 李四在运动 w.work()
// 类外部访问保护类型 let p = new Person(‘哈哈’) console.log(p.name) // 会报错提示
当构造函数修饰为 protected 时,该类只允许被继承:
class Animal { public name; protected constructor (name) { this.name = name; } } class Cat extends Animal { constructor (name) { super(name); } }
let a = new Animal(‘Jack’);
// index.ts(13,9): TS2674: Constructor of class ‘Animal’ is protected and only accessible within the class declaration.
<a name="TxDGh"></a>
#### 3. private 私有
- 使用 private 修饰的属性或方法,在子类中也是不允许访问的:
- 当构造函数修饰为 private 时,该类不允许被继承或者实例化:
```javascript
# 使用 private 修饰的属性或方法,在子类中也是不允许访问的:
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
class Cat extends Animal {
constructor(name) {
super(name);
console.log(this.name);
}
}
// index.ts(11,17): error TS2341: Property 'name' is private and only accessible within class 'Animal'
# 当构造函数修饰为 private 时,该类不允许被继承或者实例化:
class Animal {
public name;
private constructor (name) {
this.name = name;
}
}
class Cat extends Animal {
constructor (name) {
super(name);
}
}
let a = new Animal('Jack');
// index.ts(7,19): TS2675: Cannot extend a class 'Animal'. Class constructor is marked as private.
// index.ts(13,9): TS2673: Constructor of class 'Animal' is private and only accessible within the class declaration.
4. readonly
修饰符和readonly还可以使用在构造函数参数中,等同于类中定义该属性同时给该属性赋值,使代码更简洁。
readonly 只读属性关键字,只允许出现在属性声明或索引签名或构造函数中。 ```javascript class Animal { readonly name:string; public constructor(name:string) {
this.name = name;
} }
let a = new Animal(‘Jack’); console.log(a.name); // Jack a.name = ‘Tom’; // Found 1 error. Watching for file changes.
注意如果 readonly 和其他访问修饰符同时存在的话,需要写在其后面。
class Animal { // public readonly name; public constructor(public readonly name) { // this.name = name; } }
<a name="QerzZ"></a>
### 七、静态属性/方法
- TypeScript可以使用“static” 关键字标注类的静态成员
- 使用 static 修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:
- 类成员的静态属性我们可以直接调用,不能实例调用
```javascript
class Person{
public name: string;
public age: number = 20;
// 静态属性
static sex = '男';
constructor(name:string){
this.name = name
}
run(){ // 实例方法
console.log(`${this.name}在工作`)
}
work(){
console.log(`${this.name}在玩`)
}
static print(){ // 静态方法 里边没法直接调用类里边的属性, 如果需要可以改成静态属性
console.log('静态方法'+ Person.sex)
console.log('静态方法2' + Person.sex) // 静态方法中可以通过this来调用静态熟悉
}
}
let p = new Person('sss')
p.run()
Person.print()
八、类型推断
- TypeScript里,在有些没有明确指出类型的地方,类型推论会帮助提供类型
```javascript // 类型自动推论为 number类型 let s = 3;
// 如果定义时没有赋值自动推论为 any类型 let my
// 联合类型的变量再被赋值的时候,会根据推论的规则推断出一个类型: let myFavoriteNumber: string | number; myFavoriteNumber = ‘seven’; // 被推断成 string console.log(myFavoriteNumber.length); // 5 myFavoriteNumber = 7; // 被推断成 number console.log(myFavoriteNumber.length); // 编译时报错
<a name="nE6lO"></a>
### 九、抽象类
- typescript中的抽象类,他是提供其他类继承的基类,不能被直接实例化。
- 为了规定类中的一些属性和方法 写出来就是为了被继承的 也只能被继承 抽象类无法实列化 抽象类中也可以拥有一些抽象的属性和方法 和接口的主要区别是接口中无法实现方法 而抽象类的可以 关键字abstract
- abstract 用于定义抽象类和其中的抽象方法。抽象类中的抽象方法不包含具体实现并且必须在派生类中实现
- abstract 抽象方法只能放在抽象类里边
- 抽象类的子类必须实现抽象类里边的抽象方法
```javascript
abstract class Animal{ // 抽象类
public name:string
constructor(name:string){
this.name = name
}
abstract eat(): any; // 抽象方法 不包含具体实现并且必须在派生类中实现
}
// let a = new Animal() 错误写法抽象类不能被实例化
class Dog extends Animal{
// 抽象类的子类必须实现抽象类里边的抽象方法
constructor(name:any){
super(name)
}
eat(){
console.log(this.name + '吃骨头')
}
}
class Cat extends Animal{
// 抽象类的子类必须实现抽象类里边的抽象方法
constructor(name:any){
super(name)
}
eat(){ // 如果Cat子类里边不实现父类里边抽象方法 就会报错
console.log(this.name + '吃鱼')
}
}
let d = new Dog('小狗')
let c = new Cat('小猫')
c.eat()
d.eat()
十、接口
TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约。
接口得作用,在面向对象得编程中,接口是一种规范得定义,他定义了行为和动作规范,在程序设计中,接口起到一种限制和规范得作用,接口定义了某一些类所需要遵守得规范,接口不关心这些数据得内部状态数据,也不关系这些类里方法得实现细节,它只规定这批类里必须提供某些方法,提供这些方法得类就可以满足实际需要,typescript中得接口类类似于java,同时还增加了更灵活得接口类型,包括属性,函数,可索引和类等
- 接口分为:
- 属性类接口
- 函数类型接口
- 可索引接口
- 类类型接口
- 接口扩展
1. 属性接口
// 接口定义关键字interface 就是传入对象的约束 属性接口
interface FullName{
firstName:string; // 注意分号结束
secondName:string;
}
function printName(name:FullName):void{
// 必须传人一个对象,对象里边要有 firstName secondName
console.log(name.firstName + '--' + name.secondName)
}
let obj = { // 传人的参数只要包含接口的约束,也可以定义没有被约束的字段,不过不推荐那么写
age:1,
firstName: '张', // 参数的顺序可以不一样
secondName: '三'
}
// printName({ // 这样传递只能包含接口定义的字段
// age: 1, // 会报错
// firstName: '张',
// secondName: '三'
// })
printName(obj)
# 接口可选属性 想要secondName可传可不传, 就加一个可选属性 ?
interface FullName{
firstName:string; // 注意分号结束
secondName?:string;
}
function getName(name:FullName):void{
console.log(name)
}
getName({
// secondName: '三',
firstName: '张'
})
# 实现一个简单的·ajax接口规范
interface Config{
type:string;
url:string;
data?:string;
dataType:string;
}
function ajax(config:Config){
let xhr = new XMLHttpRequest()
xhr.open(config.type, config.url,true)
xhr.onreadystatechange = function(){
if(this.readyState == 4 && this.status == 200){
console.log('成功')
if(config.dataType == 'json'){
console.log(JSON.parse(this.responseText))
}else{
console.log(this.responseText)
}
}
}
xhr.send(config.data);
}
ajax({
type:'post',
url:'https://api.kkcode.com/dev/order/list',
dataType: 'json',
data: 'userId=10001'
})
2. 函数类型接口
interface Fn{
(key:string,value:string):string; // 定义一个函数接口值 key value 返回值 都是string类型
}
let md5:Fn = function(key:string,value:string):string{
// 模拟操作
return key+value
}
console.log(md5('name','zz'))
let sha1:Fn = function(key:string,value:string):string{
// 模拟操作
return key+value
}
3. 可索引接口
- 可索引接口 通俗讲就是对,数组 对象的约束 (不常用)
- 与使用接口描述函数类型差不多,我们也可以描述那些能够“通过索引得到”的类型,比如a[10]或ageMap[“daniel”]。 可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。 ```javascript
// 数组的约束 interface UserArr{ [index:number]:string; // index索引值必须是number类型 value是string类型, 如果想传任意类型 any }
let arr:UserArr = [‘111’,’2222’]
console.log(arr)
// 对象的约束 interface UserObj{
[index:string]:string
} let obj:UserObj={name:’一’,age:’30’}
<a name="YrrXV"></a>
#### 4. 类类型接口
- TypeScript也能够用它来明确的强制一个类去符合某种契约。
```javascript
// 类类型接口: 对类的约束 和 抽象类有点相似
interface Animal1{
name:string;
drink(str:string):void;
}
class Num implements Animal1{
public name:string;
public constructor(name:string){
this.name = name
}
drink(){
console.log(this.name)
}
}
let n = new Num('黑')
n.drink()
5. 接口扩展
- 接口继承和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
- 一个接口可以继承多个接口,创建出多个接口的合成接口。
```javascript
// 接口扩展,接口可以继承接口
interface Animal1{
eat():void;
}
和类一样,接口也可以相互继承。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。
interface Person extends Animal1{ // Person接口 继承 Animal1接口, 如果那个类想实现Person接口 就必须要实现Person接口和Animal1接口,里边的属性和方法 work():void; }
class Web implements Person{ public name:string; constructor(name:string){ this.name = name }
eat(){
console.log(this.name+'2222')
}
work(){
console.log(this.name+'3333')
}
} let w = new Web(‘黑’)
<a name="ZVoBJ"></a>
#### 6. 类实现接口 implements
- 实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。
```javascript
interface Alarm {
alert(): void;
}
class Door {
}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log('SecurityDoor alert');
}
}
class Car implements Alarm {
alert() {
console.log('Car alert');
}
}
// 一个类实现多个接口
interface Alarm {
alert(): void;
}
interface Light {
lightOn(): void;
lightOff(): void;
}
class Car implements Alarm, Light {
alert() {
console.log('Car alert');
}
lightOn() {
console.log('Car light on');
}
lightOff() {
console.log('Car light off');
}
}
7. type vs interface
- type 和 interface 对于能够实现的功能非常相似
- type官方定义
- interface是接口,type是类型,本身就是两个概念。只是碰巧表现上比较相似。
- 希望定义一个变量类型,就用type,如果希望是能够继承并约束的,就用interface。
- 如果你不知道该用哪个,说明你只是想定义一个类型而非接口,所以应该用type。
相同点
- 都可以描述一个对象或者函数 ```javascript interface User { name: string age: number }
interface SetUser { (name: string, age: number): void; }
type User = { name: string age: number };
type SetUser = (name: string, age: number)=> void;
2. 都允许拓展(extends)
```javascript
// 1. interface extends interface
interface Name {
name: string;
}
interface User extends Name {
age: number;
}
// 2.type extends type
type Name = {
name: string;
}
type User = Name & { age: number };
// 3.interface extends type
type Name = {
name: string;
}
interface User extends Name {
age: number;
}
// 4.type extends interface
interface Name {
name: string;
}
type User = Name & {
age: number;
}
不同点
- type 可以声明基本类型别名,联合类型,元组等类型 ```javascript // 基本类型别名 type Name = string
// 联合类型 interface Dog { wong(); } interface Cat { miao(); }
type Pet = Dog | Cat
// 具体定义数组每个位置的类型 type PetList = [Dog, Pet]
// 当你想获取一个变量的类型时,使用 typeof let div = document.createElement(‘div’); type B = typeof div
type StringOrNumber = string | number;
type Text = string | { text: string };
type NameLookup = Dictionary
type Callback
type Pair
type Coordinates = Pair
type Tree
- interface 能够声明合并
```javascript
interface User {
name: string
age: number
}
interface User {
sex: string
}
/*
User 接口为 {
name: string
age: number
sex: string
}
*/
十一、泛型
- 泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用时再去指定类型的一种特性。
- 可以把泛型理解为代表类型的参数
- 我们希望传入的值是什么类型,返回的值就是什么类型// 传入的值可以是任意的类型,这时候就可以用到 泛型
泛型,在软件工程中,我们不仅要创建一致的定义良好api,同时也要考虑可重用性,组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能
在像C#和Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据。 这样用户就可以,以自己的数据类型来使用组件。
通俗理解:泛型就是解决 类 接口 方法 的复用性,以及对不确定数据类型的支持
- 泛型定义
- 泛型函数
- 泛型类
- 泛型接口
1. 泛型定义/泛型函数
- 我们需要一种方法使返回值的类型与传入参数的类型是相同的。 这里,我们使用了 类型变量,它是一种特殊的变量,只用于表示类型而不是值。
# <T> 表示泛型,具体什么类型是调用这个方法的时候决定的
function identity<T>(arg: T): T {
return arg;
}
# 我们定义了泛型函数后,可以用两种方法使用。 第一种是,传入所有的参数,包含类型参数:
let output = identity<string>("myString"); // type of output will be 'string'
// 这里我们明确的指定了T是string类型,并做为一个参数传给函数,使用了<>括起来而不是()。
# 第二种方法更普遍。利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型:
let output = identity("myString"); // type of output will be 'string'
2. 泛型类
泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。 ```javascript // 类的泛型 泛型类看上去与泛型接口差不多。 泛型类使用( <>)括起泛型类型,跟在类名后面。 class Minclass
{ public list:T[] = []; add(value:T):void{
this.list.push(value)
}
min():T{
let minNum = this.list[0] for(let i = 0; i < this.list.length; i++){ if(minNum > this.list[i]){ minNum = this.list[i] } } return minNum
} }
let m1 = new Minclass
let m2 = new Minclass
<a name="bZrcR"></a>
#### 3. 泛型接口
```javascript
// 类型接口
// - 函数类型接口
interface ConfigFn{
(value1:string,value2:string):string;
}
let setData:ConfigFn = function(value1:string,value2:string):string{
return value1 + value2
}
console.log(setData('name','刘'))
// - 泛型接口
// -- 第一种写法
interface ConfigFn{
<T>(value1:T):T;
}
let setData:ConfigFn = function<T>(value1:T):T{
return value1
}
console.log(setData(123))
// -- 第二种写法
interface ConfigFn<T>{
(value1:T):T;
}
let setData:ConfigFn<string> = function<T>(value1:T):T{
return value1
}
console.log(setData('222'))
4. 把类当作参数类型的泛型类
// 操作数据库的泛型类
class MysqlDB<T>{
add(user:T):boolean{
console.log(user)
return true
}
}
class User{
userName:string | undefined; // 没有传值 默认值undefined
pasworld:string | undefined;
constructor(params:{
userName:string | undefined,
pasworld:string | undefined
}){
this.userName = params.userName
this.pasworld = params.pasworld
}
}
let u = new User({
userName: '张三',
pasworld: '1111'
})
// 把类当作参数类型的泛型类
let db = new MysqlDB<User>() // new MysqlDB指定传人的类型
db.add(u)
5. 泛型约束
- 有时候,我们希望类型变量对应的类型上存在某些属性。这时,除非我们显式地将特定属性定义为类型变量,否则编译器不会知道它们的存在。
```javascript
// 假设 length 属性是可用的。让我们再次使用 identity 函数并尝试输出参数的长度:
function identity
(arg: T): T { console.log(arg.length); // Error return arg; }
- 在这种情况下,编译器将不会知道 T 确实含有 length 属性,尤其是在可以将任何类型赋给类型变量 T 的情况下。我们需要做的就是让类型变量 extends 一个含有我们所需属性的接口
- T extends Length 用于告诉编译器,我们支持已经实现 Length 接口的任何类型。之后,当我们使用不含有 length 属性的对象作为参数调用 identity 函数时,TypeScript 会提示相关的错误信息
```javascript
interface Length {
length: number;
}
function identity<T extends Length>(arg: T): T {
console.log(arg.length); // 可以获取length属性
return arg;
}
- 还可以使用 , 号来分隔多种约束类型,比如:
6. keyof
- keyof 本身作为获取一个对象的所有 key 类型的关键字,本身是针对对象的。而访问一个对象,索引签名参数类型必须是 “string”、“number”、“symbol”或模板文本类型
- 获取类型 T 的所有 key 的类型集合 ```javascript type Point = { x: number; y: number }; type P = keyof Point; => type P = keyof Point
// string 不会强制转换为 number type Arrayish = { [n: number]: unknown }; type A = keyof Arrayish; => type A = number
// 在此示例中,M 是string | number — 这是因为 JavaScript 对象键总是被强制转换为字符串,所以 obj[0] 总是与 obj[“0”] 相同 type Mapish = { [k: string]: boolean }; type M = keyof Mapish; => type M = string | number
// 索引签名参数类型必须是 “string”、“number”、“symbol”或模板文本类型 type KeyOfAny = keyof any; => type KeyOfAny = string | number | symbol
// 示例
function prop
prop({ s: 1 }, ‘s’)
<a name="INBbk"></a>
#### 7. typeof
- TypeScript 添加了一个 typeof 运算符,您可以在类型上下文中使用它来引用变量或属性的类型
```javascript
let s = "hello";
let n: typeof s;
=> let n: string
let f = (name: string) => {
return 1;
};
type TypeF = typeof f;
=> type TypeF = (name: string) => number
// keyof typeof 结合使用
const COLORS = {
red: '',
blue: ''
}
// 首先通过typeof操作符获取color变量的类型,然后通过keyof操作符获取该类型的所有键,
// 即字符串字面量联合类型 'red' | 'blue'
type Colors = keyof typeof COLORS
8. 泛型工具( Partial )
- 为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等。出于篇幅考虑,这里我们只简单介绍其中几个常用的工具类型
- Partial
的作用就是将某个类型里的属性全部变为可选项 ?。 ```javascript interface Todo { title: string; description: string; }
function updateTodo(todo: T, fieldsToUpdate: Partial
const todo1 = { title: “organize desk”, description: “clear clutter” };
const todo2 = updateTodo(todo1, { description: “throw out trash”, // 经过 Partial的转换 title 变成了可传可不传 });
/*< Partial内置实现方式 >*/ /**
- node_modules/typescript/lib/lib.es5.d.ts
- Make all properties in T optional
*/
type Partial
= { [P in keyof T]?: T[P]; }; // 首先通过 keyof T 拿到 T 的所有属性名,然后使用 in 进行遍历,将值赋给 P, // 最后通过 T[P] 取得相应的属性值。中间的 ? 号,用于将所有属性变为可选。
/**/
<a name="ZvhM2"></a>
#### 9. 泛型工具( Record )
- Record<K,T>构造具有给定类型T的一组属性K的类型 及 K 中所有的属性的值转化为 T 类型。在将一个类型的属性映射到另一个类型的属性时,Record非常方便
- 他会将一个类型的所有属性值都映射到另一个类型上并创造一个新的类型.
- 可以这样理解 Record<K,V> 创建一个 新类型key是 K,value是:V
```javascript
// demo1
type petsGroup = 'dog' | 'cat' | 'fish';
interface IPetInfo {
name: string,
age: number,
}
type IPets = Record<petsGroup, IPetInfo>
=> type IPets = {
dog: IPetInfo;
cat: IPetInfo;
fish: IPetInfo;
}
const data: IPets = {
dog: {
name: 'z',
age: 1
},
cat: {
name: 'z',
age: 1
},
fish: {
name: 'z',
age: 1
}
}
// demo2
type PropItem = {
description?: string
type?: string
default?: string
required?: boolean
}
type PropRecord = Record<number, PropItem>
=> type PropRecord = {
[x: number]: PropItem;
}
10. 泛型工具( Pick )
- Pick
的作用是将某个类型中的子属性挑出来,变成包含这个类型部分属性的子类型。 ```javascript interface TState { title: string; description: string; completed: boolean; }
type NewTState = Pick
const state: NewTState = { title: “Clean room”, completed: false };
<a name="iygAT"></a>
#### 11. 泛型工具 ( Exclude )
- Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉
- 如果 T 能赋值给 U 类型的话,那么就会返回 never 类型,否则返回 T 类型。最终实现的效果就是将 T 中某些属于 U 的类型移除掉
```javascript
type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
type T2 = Exclude<string | number | (() => void), Function>; // string | number
12. 泛型工具 ( ReturnType )
- ReturnType
的作用是用于获取函数 T 的返回类型。 type T0 = ReturnType<() => string>; // string
13. 泛型命名规范
- T 代表 Type,在定义泛型时通常用作第一个类型变量名称。但实际上 T 可以用任何有效名称代替。除了 T 之外,以下是常见泛型变量代表的意思
- K(Key):表示对象中的键类型;
- V(Value):表示对象中的值类型;
- E(Element):表示元素类型。
- 也不是规定死的 也可以用其他的来代替只要语义好就OK。
十二、模块
- 关于术语的一点说明: 请务必注意一点,TypeScript 1.5里术语名已经发生了变化。 “内部模块”现在称做“命名空间”。 “外部模块”现在则简称为“模块”,这是为了与 ECMAScript 2015里的术语保持一致,(也就是说 module X { 相当于现在推荐的写法 namespace X {)。
1. 简介
从ECMAScript 2015开始,JavaScript引入了模块的概念。TypeScript也沿用这个概念。
模块在其自身的作用域里执行,而不是在全局作用域里;这意味着定义在一个模块里的变量,函数,类等等在模块外部是不可见的,除非你明确地使用
[export](https://www.tslang.cn/docs/handbook/modules.html#export)
形式之一导出它们。 相反,如果想使用其它模块导出的变量,函数,类,接口等的时候,你必须要导入它们,可以使用[import](https://www.tslang.cn/docs/handbook/modules.html#import)
形式之一。模块是自声明的;两个模块之间的关系是通过在文件级别上使用imports和exports建立的。
模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。 大家最熟知的JavaScript模块加载器是服务于Node.js的 CommonJS和服务于Web应用的Require.js
TypeScript与ECMAScript 2015一样,任何包含顶级
import
或者export
的文件都被当成一个模块。相反地,如果一个文件不带有顶级的import
或者export
声明,那么它的内容被视为全局可见的(因此对模块也是可见的)。
2. 导入导出模块
// test.ts index.ts 同一级目录
// test.ts 导出
let str:string = 'ssss'
let num:number = 1111
export {
str,
num
}
// index.ts 导入
import {str, num} from './test'
console.log(str,num)
3. 命名空间
- 如何在TypeScript里使用命名空间(之前叫做“内部模块”)来组织你的代码。 就像我们在术语说明里提到的那样,“内部模块”现在叫做“命名空间”。 另外,任何使用 module关键字来声明一个内部模块的地方都应该使用namespace关键字来替换。 这就避免了让新的使用者被相似的名称所迷惑。
```javascript
// 命名空间
/*
- 在代码量较大的情况下,为了避免各种命名相冲突,可将相似功能的函数,类,接口放置到命名空间
- 同java的包, .Net的命名空间一样,typescript的命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象,命名空间的对象通过export导入
命名空间和模块的区别
- 命名空间: 内部模块,主要用于组织代码,避免冲突
- 模块: ts的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间
*/
namespace A{ // 命名空间 export class Dog{ // 命名空间中的属性 类 方法都是私有的 像在外部使用需要 export导出 public name:string; constructor(name:string){ this.name = name }
getName():void{
console.log('this.name',this.name)
}
}
}
let D = new A.Dog(‘鼠标’) D.getName()
// 用命名空间区分开 namespace B{ class Dog{ public name:string; constructor(name:string){ this.name = name }
getName():void{
console.log('this.name',this.name)
}
}
}
命名空间导出
export namespace B{ class Dog{ public name:string; constructor(name:string){ this.name = name } } }
命名空间导入
import {B} from ‘./b’
<a name="VcwVM"></a>
### 十三、装饰器
随着TypeScript和ES6里引入了类,在一些场景下我们需要额外的特性来支持标注或修改类及其成员。 装饰器(Decorators)为我们在类的声明及成员上通过元编程语法添加标注提供了一种方式。 Javascript里的装饰器目前处在 [建议征集的第二阶段](https://github.com/tc39/proposal-decorators),但在TypeScript里已做为一项实验性特性予以支持。
<a name="ZI5Sj"></a>
#### 1. 装饰器
- _装饰器_是一种特殊类型的声明,它能够被附加到[类声明](https://www.tslang.cn/docs/handbook/decorators.html#class-decorators),[方法](https://www.tslang.cn/docs/handbook/decorators.html#method-decorators), [访问符](https://www.tslang.cn/docs/handbook/decorators.html#accessor-decorators),[属性](https://www.tslang.cn/docs/handbook/decorators.html#property-decorators)或[参数](https://www.tslang.cn/docs/handbook/decorators.html#parameter-decorators)上。可以修改类型的行为 装饰器使用 `@expression`这种形式,`expression`求值后必须为一个函数,它会在运行时被调用,被装饰的声明信息做为参数传入。
- 通俗的讲装饰器就是一个方法,可以注入到类,方法,属性参数上来扩展类,属性方法,参数的功能
- 常见的装饰器有:类装饰器,属性装饰器,方法装饰器,参数装饰器
- 装饰器的写法: 普通装饰器(无法传参), 装饰器工厂(可传参)
<a name="l1KLg"></a>
#### 1. 类装饰器
- 装饰器可以动态的扩展我们的类的属性和方法,在不修改类的前提下来扩展我们的功能
```javascript
# 定义装饰器 普通装饰器无法传参数
function logClass(params:any){
// params就是当前类
params.prototype.apiUrl = '动态扩展的属性'
params.prototype.run = function(){
console.log('run方法')
}
console.log('logClass',params)
}
@logClass
class HttpClient{
constructor(){
}
getData(){
}
}
let h:any = new HttpClient()
h.run()
console.log(h.apiUrl)
# 装饰器工厂 可传递参数
- 如果我们要定制一个修饰器如何应用到一个声明上,我们得写一个装饰器工厂函数。 装饰器工厂就是一个简单的函数,它返回一个表达式,以供装饰器在运行时调用。
function logClass(params:string){ // 这是一个装饰器工厂
return function(target:any){ // 装饰器
// return返回的这个函数就是logClass
console.log('logClass',params)
console.log('logClass',target)
target.prototype.api = params
}
}
@logClass('hello world')
class HttpClient{
constructor(){
}
getData(){
}
}
let h:any = new HttpClient()
console.log(h.api) // hello world
3. 类装饰器重载构造函数
// 类装饰器
// - 下面是一个重载类构造函数的例子
// - 类装饰器表达式会在运行时当作函数调用,类的构造函数作为其唯一的参数
// - 如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明
function logClass(target: any) {
console.log('logClass', target)
return class extends target {
apiUrl: any = '我是修改后的数据'
getData(){
console.log('getData', this.apiUrl)
}
}
}
@logClass
class HttpClient {
public apiUrl: string | undefined;
constructor() {
this.apiUrl = '我是构造函数里面的apiUrl'
}
getData(): void {
console.log('getData', this.apiUrl)
}
}
let h = new HttpClient()
h.getData()
4. 属性装饰器
属性装饰器声明在一个属性声明之前(紧靠着属性声明)。 属性装饰器不能用在声明文件中(.d.ts),或者任何外部上下文(比如
declare
的类)里。属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
- 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
成员的名字。 ```javascript // 类装饰器 function logClass(params:string){
return function(target:any){
// console.log('logClass',params) // console.log('logClass',target)
} }
// 属性装饰器 function logProperty(params:any){ return function(target:any,name:any){ console.log(‘target’,target) console.log(‘name’,name)
target[name] = params
}
}
@logClass(‘hello world’) class HttpClient{ @logProperty(‘属性装饰器1111’) // 装饰器后边不需要加分号 // static url:string | undefined; // 对于静态成员来说是类的构造函数 public url:string | undefined; // 对于实例成员是类的原型对象。 constructor(){
}
getData(){
console.log('this.url',this.url) // 属性装饰器1111
}
}
let http = new HttpClient()
http.getData()
<a name="GltrU"></a>
### 十四、操作符
<a name="NFRWi"></a>
#### 1. ( ?? ) 空值合并运算符
- TS 3.7版本正式支持使用
- || 运算符的缺点: 当左侧表达式的结果是数字 0 或空字符串时,会被视为 false。
- 空值合并运算符:当左侧操作数为 null 或 undefined 时,其返回右侧的操作数,否则返回左侧的操作数。
```javascript
const foo = null ?? 'default string';
console.log(foo); // 输出:"default string"
const baz = 0 ?? 42;
console.log(baz); // 输出:0
2. ( ! ) 非空断言操作符
- 在上下文中当类型检查器无法断定类型时,一个新的后缀表达式操作符 ! 可以用于断言操作对象是非 null 和非 undefined 类型。具体而言,x! 将从 x 值域中排除 null 和 undefined
```javascript // 1.忽略 undefined 和 null 类型 function myFunc(maybeString: string | undefined | null) { // Type ‘string | null | undefined’ is not assignable to type ‘string’. // Type ‘undefined’ is not assignable to type ‘string’. const onlyString: string = maybeString; // Error 不能将类型“string | null | undefined”分配给类型“string”。不能将类型“undefined”分配给类型“string” const ignoreUndefinedAndNull: string = maybeString!; // Ok }
// 2.调用函数时忽略 undefined 类型 type NumGenerator = () => number; function myFunc(numGenerator: NumGenerator | undefined) { // Object is possibly ‘undefined’.(2532) // Cannot invoke an object which is possibly ‘undefined’.(2722) const num1 = numGenerator(); // Error const num2 = numGenerator!(); //OK }
// 3.因为 ! 非空断言操作符会从编译生成的 JavaScript 代码中移除,所以在实际使用的过程中,要特别注意。比如下面这个例子: const a: number | undefined = undefined; const b: number = a!; console.log(b); of “use strict”; var a = undefined; var b = a; console.log(b);
<a name="DPnEx"></a>
#### 3. ( ?. ) 可选链操作符
- 可选链操作符( ?. )允许读取位于连接对象链深处的属性的值,而不必明确验证链中的每个引用是否有效。?. 操作符的功能类似于 . 链式操作符,不同之处在于,在引用为空(nullish ) (null 或者 undefined) 的情况下不会引起错误,该表达式短路返回值是 undefined。与函数调用一起使用时,如果给定的函数不存在,则返回 undefined
```javascript
const obj:any = {
foo: {
bar: {
baz: 42,
},
},
};
const fn:any = null
console.log('A', obj?.foo?.bar?.baz) // 42
console.log('B',obj?.qux?.baz) // undefined
console.log('C', fn?.()) // undefined
4. ( ? ) 可选属性操作符
- 在面向对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类去实现。 TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。 ```javascript interface Person { name: string; age?: number; }
let nordon: Person = { name: “nordon” };
<a name="Zdlrt"></a>
#### 5. ( & ) (叠加类型)
- 在 TypeScript 中交叉类型是将多个类型合并为一个类型。通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
```javascript
type PartialPointX = { x: number; };
type Point = PartialPointX & { y: number; } & Z;
interface Z{
z: number
}
let point: Point = {
x: 1,
y: 1,
z: 2
}Ï
6. ( | ) 分割符
- 在 TypeScript 中联合类型(Union Types)表示取值可以为多种类型中的一种,联合类型使用 | 分隔每个类型。联合类型通常与 null 或 undefined 一起使用: ```javascript const sayHello = (name: string | number) => {
}; sayHello(‘1’) sayHello(1) sayHello(null) // 类型“null”的参数不能赋给类型“string | number”的参数
<a name="GLxhT"></a>
#### 7. ( _ ) 数字分隔符
- TypeScript 2.7 带来了对数字分隔符的支持,正如数值分隔符 ECMAScript 提案中所概述的那样。对于一个数字字面量,你现在可以通过把一个下划线作为它们之间的分隔符来分组数字
- 虽然数字分隔符看起来很简单,但在使用时还是有一些限制。比如你只能在两个数字之间添加 _ 分隔符
- 也不能连续使用多个 _ 分隔符
```javascript
const inhabitantsOfMunich = 1_464_301;
const distanceEarthSunInKm = 149_600_000;
const fileSystemPermission = 0b111_111_000;
const bytes = 0b1111_10101011_11110000_00001101;
// 分隔符不会改变数值字面量的值,但逻辑分组使人们更容易一眼就能读懂数字。以上 TS 代码经过编译后,会生成以下 ES5 代码:
"use strict";
var inhabitantsOfMunich = 1464301;
var distanceEarthSunInKm = 149600000;
var fileSystemPermission = 504;
var bytes = 262926349;
8. ( in )
- in 也是一个 类型关键词, 可以对联合类型进行遍历,只可以用在 type 关键词下面 ```javascript type Person = {
[key in 'name' | 'age']: number
}
let obj: Person = { name: 1, age: 1 }
<a name="j3t4V"></a>
#### 9. ( [] )
- 使用 [] 操作符可以进行索引访问,也是一个 类型关键词
```javascript
interface Person {
name: string
age: number
}
type x = Person['name'] // x is string
10. ( infer )
- 在有条件类型的 extends 子语句中,允许出现 infer 声明,它会引入一个待推断的类型变量。 这个推断的类型变量可以在有条件类型的 true 分支中被引用。
infer 关键字让我们拥有深入展开泛型的结构,并 Pick 出其中任何位置的类型,并作为临时变量用于最终返回类型的能力
感觉有点懵,进一步了解什么是条件类型 ```javascript //1. T extends U ? X : Y 的表达式就是条件类型
// 解读:ConditionType接收一个泛型T 返回泛型 T 继承一个条件类型为true一项
// 1,T 等于 Boolean 返回 number
// 2,T 不等于 Boolean 返回 undefined
type ConditionType
const a: boolean = false const b: string = ‘false’
type TypeA = typeof a // boolean type TypeB = typeof b // string
type T1 = ConditionType
- infer 推断值类型
```javascript
type ConditionType<T> = T extends (infer R) ? R : any;
type TypeC = object
type T3 = ConditionType<TypeC> // type T3 = object
- infer 推断函数
```javascript
// 参数类型
type ParamsType
any> = T extends (…args: infer R) => any ? R : any;
const add = (x: number, y: number) => x + y
type TypeAdd = typeof add // (a: number, b: string) => string
type T1 = ParamsType
// 返回类型
type ReturnType
- infer 推断数组里每一项
```javascript
type ArrayElementType<T> = T extends (infer E)[] ? E : T;
type arr = [number, string, boolean]
type T1 = ArrayElementType<arr> // type T1 = string | number | boolean
十五、声明文件
- 我们在ts项目中使用非ts编写的类库/没有声明文件,必须为这个类库编写类型声明文件,对外暴露他的API,否则无法使用
- 库使用方式
- 全局库 通过