# Typescript

TIP

TypeScript是Javascript的超集,遵循最新的ES5/ES6规范。Typescript扩展了Javascript语法

# 一. 环境配置

# 1 全局编译

# 全局安装
npm install typescript -g

# 生成tsconfig.json配置文件
tsc --init

# 可以将ts文件编译成js文件
tsc

# 监控ts文件变化生成js文件
tsc --watch

# 2 node环境编译

# 第一步 vscode 安装 code runner插件

# 第二步 安装ts-node
npm install ts-node -g

# 第三步 选中右键菜单 Run Code执行代码

# 3 Rollup编译

  • 目录结构
├─dist
├─public
│  └─index.html
└─src
│  │  index.ts
├─package.json        # 配置运行命令 
├─rollup.config.js    # rollup配置文件
├─tsconfig.json       # ts配置文件 更改为esnext
  • 安装依赖
npm install rollup typescript rollup-plugin-typescript2 @rollup/plugin-node-resolve rollup-plugin-serve -D
  • rollup配置
import ts from 'rollup-plugin-typescript2'; // 解析ts的插件
import {nodeResolve} from '@rollup/plugin-node-resolve'; // 解析第三方模块的插件
import serve from 'rollup-plugin-serve'; // 启动本地服务的插件
import path from 'path';

export default {
	input: 'src/index.ts', // 入口
	output: {
		format: 'iife', // 打包格斯:立即执行 自执行函数
		file: path.resolve(__dirname, 'dist/bundle.js'), // 出口文件
		sourcemap: true // 根据源码产生映射文件
	},
    plugins:[
		nodeResolve({// 第三方文件解析
			extensions: ['.js', '.ts']
		}),
		ts({ // ts配置文件
			tsconfig: path.resolve(__dirname, 'tsconfig.json')
		}),
		serve({ // 静态服务配置
			openPage: '/public/index.html',
            contentBase: '',
            port: 3000
		})
	]
}
  • tsconfig.json配置
{
  "compilerOptions": {
    "target": "es5", // 编译的目标
    "module": "ESNext", // rollup支持的模块格式
    "sourceMap": true, // 查看源代码
    "strict": true, // 严格模式
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  }
}
  • 修改package.json配置
"scripts": {
	"dev": "rollup -c -w"
},
  • 启动程序
npm run start
  • 访问
http://localhost:3000/public/index.html

# 二. 数据类型

TS中冒号后面的都为类型标识

# 1 布尔、数字、字符串

// 布尔: true 和 false
let bool:boolean = true;
// 数字
let num:number = 10;
// 字符串
let str:string = 'test';

# 2 数组

// 只能存放数字
let arr1:number[] = [1, 2, 3];
// 只能存放字符串
let arr2:string[] = ['1', '2', '3'];

// 声明联合类型
let arr3:(number|string)[] = [1, 2, '3', 4];
let arr4:Array<number|string> = [1, 2, '3', 4];

# 3 元祖

// 用于限制长度个数、类型一一对应
let tuple:[string, number, boolean] = ['test', 10, true];
// 操作元祖只能通过方法,不能通过索引,而且放入的元素必须是已经声明过的类型
tuple.push(true);

# 4 枚举

// 普通枚举,用于语义化声明,默认从0开始计数
enum USER_ROLE {
    USER, // 默认从0开始
    ADMIN,
    MANAGER
}
console.log(USER_ROLE.USER); // 0 》正举
console.log(USER_ROLE[0]); // 0 》反举
console.log(USER_ROLE);
// {0: "USER", 1: "ADMIN", 2: "MANAGER", USER: 0, ADMIN: 1, MANAGER: 2}

// 编译结果
var USER_ROLE;
(function (USER_ROLE) {
	USER_ROLE[USER_ROLE["USER"] = 0] = "USER";
	USER_ROLE[USER_ROLE["ADMIN"] = 1] = "ADMIN";
	USER_ROLE[USER_ROLE["MANAGER"] = 2] = "MANAGER";
})(USER_ROLE || (USER_ROLE = {}));
// 异构枚举
enum USER_ROLE {
    USER = 'user',
    ADMIN = 1, // 遇到数字,后面会自动推断进行叠加
    MANAGER,
}
console.log(USER_ROLE);
// index.ts:53 {1: "ADMIN", 2: "MANAGER", USER: "user", ADMIN: 1, MANAGER: 2}
// 常量枚举 》 加上const变为常量枚举,不会生成对象,更加简洁和代码量更少
const enum USER_ROLE {
    USER = 'user',
    ADMIN = 1, // 遇到数字,后面会自动推断进行叠加
    MANAGER,
}
console.log(USER_ROLE.USER);
// 编译结果如下:
// console.log("user" /* USER */);

# 5 any

// any类型:不进行类型检测的类型,相当于没有写类型
let arr:any = ['test', 11, true, {}]; // 能不写any 尽量不写any

# 6 null、undefined

// 默认情况下null和undefined是所有类型的子类型
// 例如:可以把他们赋值给number类型的变量
let str2:number;
str2 = undefined;

// 在严格模式下,只能将null 和 undefined 赋予给 null undefined
let u: undefined = undefined;
let n: null = null;

# 7 void

// void 表示函数的返回值,也可以描述变量,只能接受null,undefined
// 函数默认的返回值是undefined, 默认在严格模式下不能将null 赋给void
function getVoid(): void {
	return undefined;
}

// 用于变量的情况,一般很少用到
let a:void;
a = undefined;

# 8 never

// never是任何类型的子类型,可以把never赋予给任何类型,但是不能把其他类型赋值给never
// never代表代码无法到达终点,无法执行到结尾,有三种情况:1 错误 2 死循环 3 类型判断时会出现never
function throwError():never{
    throw new Error("");
}

function whileTrue():never{
    while (true) { }
}

function byType(val:string|number){
    if (typeof val == 'string') {
        val
    } else if (typeof val == 'number') {
        val
    } else {
        val // never 》 帮助代码做完整校验
    }
}
let n:never = throwError();

# 9 symbol

// Symbol 表示独一无二 元编程会用到 stringToFlag iterator ....
let s1:symbol = Symbol('123');
let s2:symbol = Symbol('123');
console.log(s1 == s2); // false

# 10 bigint

// BigInt
export let num1:bigint = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(1);
let num2 = BigInt(Number.MAX_SAFE_INTEGER) + BigInt(2);

# 11 object

// 表示非原始类型
const create = (obj:object):void => {}
create({});
create([]);
create(function(){})

# 12 字面量

// 可以用字面量当做类型,同时也表明只能采用这几个值(限定值)。类似枚举
type Direction = 'Up' | 'Down' | 'Left' | 'Right';
let direction:Direction = 'Down';

# 三. 类型推导

# 1 类型推导

// 声明变量没有赋予值时默认变量是any类型
let name; // 类型为any
name = 'hello'
name = 11;

// 声明变量赋值时则以赋值类型为准
let name = 'hello'; // name被推导为字符串类型 
name = 10; // 报错

# 2 包装对象

// 在使用基本数据类型时,调用基本数据类型上的方法,默认会将原始数据类型包装成对象类型
11..toString() // Number(11).toString()

let number1:number = 11;
let number2:Number = 11;
let number3:number = Number(11); // 11
// let number4:number = new Number(11)// {} 错误语法 不能把实例赋予给基本类型
// 类也是一个类型 他可以描述实例
let number5:Number = new Number(11)

# 3 联合类型

  • 联合类型,访问的时候,只能访问共有属性和方法,定义需要满足其中一种的全部
  • 联合类型使用 |
// 在使用联合类型时,没有赋值只能访问联合类型中共有的方法和属性
let name:string | number // 联合类型  
console.log(name!.toString()); // 公共方法

// 初始化了以后就可以调用各自类型的方法
name = 10;
console.log(name!.toFixed(2)); // number方法
name = 'zf';
console.log(name!.toLowerCase()); // 字符串方法

// 访问共同成员
interface Women{
	age: number,
	cry(): void
  }
  interface Man{
	age: number,
  }
  declare function People(): Women | Man;
  let people = People();
  people.age = 18; //ok
// eople.cry();//error 非共同成员

// 定义需要满足其中一个的全部
interface Person1 {
	handsome: string,
	name: string
}
interface Person2 {
	height: string,
	name: string
}
type Person3 = Person1 | Person2;
let person: Person3 = {
	name: 'test',
	height: 'string', // 不加会报错
}
console.log(person.name);

# 4 交叉类型

  • 交叉类型是将多个类型合并为一个类型,可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
  • 交叉类型获取所有的属性和方法
  • 交叉类型使用 &
// 全部包含
interface Person1 {
    handsome: string
}
interface Person2 {
    height: string
}
type Person3 = Person1 & Person2;
let person: Person3 = {
    handsome: '帅',
    height: '高',
}
console.log(person.handsome);
console.log(person.height);

// 方法的mixin 默认推断会生成交集
function mixin<T extends object, K extends object>(o1: T, o2: K): T & K {
	return { ...o1, ...o2 }
}

# 四 断言

# 1 非空断言

let ele: HTMLElement | null = document.getElementById('#app');

// 非空断言 使用!表示,表示一定有值
ele!.style.color = 'red'; // 断定ele元素一定有值

# 2 类型断言

let ele: HTMLElement | null = document.getElementById('#app');

// 第一种使用as,强制断言,因为断言不能断言不存在的属性
(ele as HTMLElement).style.color = 'red';

// 第二种使用<>,但是和 jsx 语法有冲突,尽量不采用
(<HTMLElement>ele).style.color = 'red';

// 尽量使用第一种类型断言,因为在react中第二种方式会被认为是jsx语法

# 3 双重断言

// 双重断言 (不建议使用 会破坏原有类型)
(ele as any) as boolean; // 断言为any是因为any类型可以被赋值给其他类型

# 五 函数

# 1 声明方式

// 通过function关键字来进行声明
 function sum(a:string, b:string):string { // 函数括号后面的是返回值类型
    return a + b;
}
sum('a','b')

// 通过表达式方式声明
type Sum = (a1: string, b1: string) => string;
let sum: Sum = (a: string, b: string) => {
    return a + b;
};
sum('a','b')

# 2 可选参数

// 可选参数使用 ?
let sum = (a: string, b?: string):string => {
    return a + b;
};
sum('a'); // 可选参数必须在其他参数的最后面

# 3 默认参数

// 默认参数使用 =
let sum = (a: string, b: string = 'b'): string => {
    return a + b;
};
sum('a'); // 默认参数必须在其他参数的最后面

# 4 剩余参数

// 剩余参数使用 ...
const sum = (...args: string[]): string => {
    return args.reduce((memo, current) => memo += current, '')
}
sum('a', 'b', 'c', 'd')

# 5 重载

  • 根据传入不同的参数,返回不同的结果
// 希望把一个字符串 或者数字转换成一个数组
// 123 => [1,2,3];
// '123'=>['1','2','3']

// 重载中间不能插入其他语句
function toArray(value: number): number[]
function toArray(value: string): string[]
function toArray(value: number | string) {
    if (typeof value == 'string') {
        return value.split('');
    } else {
        return value.toString().split('').map(item => parseInt(item))
    }
}
toArray(123);
toArray('123');

# 六 类

# 1 定义

  • class 关键词声明类
  • 实例上的属性需要先声明在使用,构造函数中的参数可以使用可选参数和剩余参数
class Pointer{
    x!: number; // 实例上的属性必须先声明,再使用,
	y!: number;
	// 构造函数可以使用可选参数和剩余参数
    constructor(x: number, y?: number, ...args: number[]){
        this.x = x;
        this.y = y as number; // 如果使用了可选参数,这里需要明确类型
    }
}
let pointer = new Pointer(100,200);
console.log(pointer.y,pointer.x);
  • 修饰符直接放到构造函数中,就无需再次声明
class Pointer { 
    constructor(public x:number, public y:number){
        this.x = x;
        this.y = y;
    }
}
let pointer = new Pointer(100,100)
console.log(pointer.y,pointer.x)

# 2 修饰符

  • public:公有修饰符,也是默认修饰符,表示自己、子类、子类之外都可以访问
  • protected:受保护的修饰符,表示自己和子类中可以访问
  • private:私有修饰符,表示只有自己可以访问
  • readonly:只读修饰符,表示在初始化完了之后就不能修改了,但是如果是对象可以更改里面的属性
/ 父类
class Animal {
	public name: string;
    protected age: number;
    private weight: number;
    public readonly height: number = 100; // 只读属性必须在声明时或构造函数里被初始化
    protected constructor(name: string, age: number, weight: number) {
        this.name = name;
        this.age = age;
        this.weight = weight;
    }
	getWeight() {
        console.log(this.weight); // 自己私有属性,自己内部可以访问
	}
	setHeight() {
		// this.height = 200; // 只读属性不能修改
	}
}

// 子类
class Cat extends Animal {
	private money: number;
    constructor(name: string, age: number, weight: number, money: number) {
        super(name, age, weight);
        this.money = money;
    }
    getName() {
        console.log(this.name); // 父类公有属性,子类可以访问
    }
    getAge() {
        console.log(this.age); // 父类受保护的属性,子类可以访问
    }
    getWeight() {
        // console.log(this.weight); // 父类私有属性,子类不能访问
	}
}

let cat = new Cat('ha', 9, 100, 100);
console.log(cat.name); // 父类公有属性,外部可以访问
// console.log(cat.age); // 父类受保护的属性,外部无法访问
// console.log(cat.weight); // 父类私有属性,外部无法访问
// console.log(cat.money); // 子类私有属性,外部无法访问

# 3 静态属性和方法

// 静态属性和静态方法是可以被子类所继承的
class Dog {
	// 静态属性
	static type = 'dog';
	// 静态方法
    static getName() {
        return 'name';
    }
}
// 通过类名.进行访问
console.log(Dog.type);
console.log(Dog.getName());

# 4 Super属性

  • super 默认在构造函数中和静态方法中都指向自己的父类, 在原型方法中super指向父类的原型
// 父类
class Animal {
	static getType(){
        return '动物'
    }
    say(message:string){
        console.log(message);
    } 
   
}
// 子类
class Cat extends Animal {
	constructor() {
		super(); // 指向父类
	}
	static getType(){
		// 静态方法中的super指代的是父类
        return super.getType()
    }
    say(){
		// 原型方法中的super指代的是父类的原型
        super.say('猫猫叫');
    }
   
}
console.log(Cat.getType())
let cat = new Cat();
console.log(cat.say())

# 5 访问器

class Dog {
	private str: string = '';
	// get 和 set 》 属性访问器
    get content() {
        return this.str;
    }
    set content(newVal: string) {
        this.str = newVal;
    }
}
let dog = new Dog();
dog.content = 'hhh'; // 设值
console.log(dog.content); // 取值

# 6 装饰器

  • 装饰类可以给类扩展功能,需要开启experimentalDecorators:true

  • 装饰器作用就是扩展类、扩展类中的属性和方法 , 不能修饰函数,函数会有变量提升的问题

  • 装饰器必须为一个函数,或者返回一个函数,装饰立刻执行,多个从下往上依次执行

  • 装饰类

function addSay1(val: any) {
    console.log(1);
}
function addSay2(val: string) {
    console.log(val)
    return function (target: any) {
        console.log(2)
    }
}

// 洋葱模型
@addSay1 // = addSay1(Person)
@addSay2('a2') // = addSay2('a2')(Person);
class Person {

}
// 如果装饰器返回一个函数,初始化从上往下执行,装饰的时候从下往上执行
// 输出结果为:a2 2 1
  • 装饰属性
// 装饰实例属性,target => 类的原型, key就是修饰的属性
function toUpperCase(target:any, key:string){
    let value = target[key]; 
    Object.defineProperty(target, key, {
        get(){
            return value.toUpperCase();
        },
        set(newValue){
            value = newValue
        }
    })
}

// 装饰静态属性,target => 类本身,key就是修饰的属性
function double(num: number) {
    return function (target: any, key: string) {
        let v = target[key]
        Object.defineProperty(target, key, {
            get() {
                return num * v
            }
        })
    }
}

class Person {
	@toUpperCase
	public name: string = 'test'
	@double(3)
	public static age: number = 18; // 静态的通过类来调用
}

let person = new Person
console.log(person.name);
console.log(Person.age);
  • 装饰方法
// 装饰方法,target => 原型
function Enum(bool: boolean) {
	// Object.definePropert(target, key, descriptor)
    return function (target: any, key: string, descriptor: PropertyDescriptor) {
		console.log(descriptor)
		descriptor.enumerable = bool;
		descriptor.writable = bool;
    }
}

class Person {
	public name: string = 'test'

	@Enum(false)
    drink() {
		return this.name;
	}
}

let person = new Person
console.log(person.drink());
  • 装饰参数
// 装饰参数,target => 原型,key => drinnk,index => 0
function params(target: any, key: string, index: number) { 
	console.log(key, index)
}

class Person {
    drink(@params content: any) {
		console.log(content);
	}
}

let person = new Person
console.log(person.drink('water'));

# 7 抽象类和方法

  • 抽象类无法被实例化new,只能被继承,使用abstract来标记
  • 抽象方法不能在抽象类中实现,只能在抽象类的具体子类中实现,而且必须实现
  • 抽象属性只能存在于类被标记为abstract中
// 抽象类
abstract class Animal{
	// 抽象方法
    abstract speak():void
	// 抽象属性
	abstract name: string
}
class Cat extends Animal {
	name: string = 'cat';
    speak(){
        console.log('猫猫叫');
    }
}
class Dog extends Animal{
	name: string = 'dog';
    speak():string{
        console.log('汪汪叫');
        return 'wangwang'
    }
}
let cat = new Cat();
cat.speak();
let dog = new Dog();
dog.speak();

# 七 接口

  • 接口可以在面向对象编程中表示行为的抽象,也可以描述对象的形状
  • 接口的作用就是为这些类型命名和为你的代码或第三方代码定义契约
  • 接口中不能含有具体的实现逻辑

# 1 接口描述对象

// 定义接口,描述对象的类型,注意:接口不能用在联合类型中
interface IVegetables {
	readonly color:string, // 只读属性
    taste:string // 普通属性
}
// 使用
const tomato: IVegetables = {
	color: 'red',
	taste: 'sweet'
}
// tomato.color = 'green'; // 仅读属性不能进行修改

# 2 接口描述函数

  • 描述函数的参数
// 通过接口定义参数对象,来限制函数的参数类型
interface ISum {
	a: string,
	b: string
}
const sum = (obj: ISum): string => {
	return obj.a + obj.b
}
console.log(sum({a: 'a', b: 'b'}));
  • 描述函数的类型
// 通过接口定义函数,来限制函数的参数类型和返回值类型
interface ISum {
	(a: string, b: string): string
}
const sum:ISum = (a, b) => {
	return a + b
}
console.log(sum('a', 'b'));

# 3 接口混合类型

// 接口中的混合类型 
interface ICount {
    (): number, // 限制函数类型
    count: number // 限制函数上的属性
}

// 函数返回函数,一般要标识函数的返回类型
const fn: ICount = (() => { 
    return ++fn.count;
}) as ICount
fn.count = 0;

console.log(fn()); // 1
console.log(fn()); // 2

# 4 接口直接断言

// 定义一个接口,然后使用的时候,as断言为当前类型
interface IVegetables {
    color:string,
    taste:string
}
// 直接断言,断言后可以直接使用 (要保证接口中限制的数据必须要有)
const tomato:IVegetables = {
    color:'red',
    taste:'sweet',
    size:'big' // 直接断言size点不出,被忽略了
} as IVegetables

# 5 接口的合并

// 接口的合并方便接口进行扩展和复用
// 定义一个接口
interface IVegetables {
    color:string,
    taste:string
}
// 再定义一个同名的接口,两个接口会合并,并改变原接口
interface IVegetables {
    size:string
}
const tomato:IVegetables = {
    color:'red',
    taste:'sweet',
    size:'big' // size可以被点出来了
}

# 6 接口的继承

// 接口的继承也是为了方便扩展和复用
// 定义一个接口
interface IVegetables {
    color:string,
    taste:string
}
// extends用于继承前面的接口,然后扩展自己的属性
interface ITomato extends IVegetables{
    size:string
}
const tomato:ITomato = {
    color:'red',
    taste:'sweet',
    size:'big' // size可以被点出来了
}

# 7 接口的可选属性

// 接口的可选属性通过 ? 来定义
interface IVegetables {
    color: string,
    taste: string,
    size?: string, // 可选属性
    id?: number // 可选属性
}
const tomato: IVegetables = {
    color: 'red',
    taste: 'sweet',
}

# 8 接口的任意属性

// 接口的任意属性可以对某一部分必填属性做限制,其余的可以随意增减
// 接口的任意属性同定义key和值为any来实现
interface IVegetables {
    color: string,
    taste: string,
    [key: string]: any // 任意接口 可多填
}
const tomato: IVegetables = {
    color: 'red',
	taste: 'sweet',
	id: 1 // 点不出来
}

# 9 接口的可索引

// 可索引的接口用于标识数组
interface ILikeArray {
    [key: number]: any
}
let arr: ILikeArray = [1, 'd', 'c']
let arr1: ILikeArray = { 0:1, 1:'2', 3:'3' };

# 10 接口的嵌套

  • 接口中的类型 可以通过类型别名的方式拿出来 , 但是只能用[]
// 定义类型别名
type MyType = {
	key: string,
	value: string
}
interface XXX {
    n: MyType[]
}
interface IArr {
    arr: MyType[], // 别名类型
    a: XXX // 接口嵌套
}
type My = IArr['a']['n']

# 11 接口的实现

  • 接口可以被类来实现, 接口中的方法都是抽象(没有具体实现)的
interface ISpeakable {
    name: string,
    // 用接口来形容类的时候,void 表示不关心返回值
    speak(): void // 描述当前实例上的方法,或者原型的方法
}
interface IChineseSpeakable {
	speakChinese(): void
}
// 类本身需要实现接口中的方法
class Speak implements ISpeakable, IChineseSpeakable {
    speakChinese(): void {
        throw new Error("Method not implemented.");
    }
	name!: string
	// 此方法是原型方法
    speak(): string {
        return 'xxx'
    }
}
let s = new Speak()

# 12 接口描述实例

interface IPerson {
    new(name: string): Person
}
function createInstance(clazz: IPerson, name: string) {
    return new clazz(name)
}
class Person {
    getName(){
		return this.name;
	}
    constructor(public name: string) { }
}
let r = createInstance(Person, '张三');
console.log(r.getName(), r.name);

# 八 泛型

  • 在某些定义的时候不确定类型,而只有在调用的时候才能确定类型的时候,就需要使用泛型来传参了
  • 泛型在声明的时候需要用 <>包裹起来,传值的时候也需要
  • 如果泛型不传参是unkown类型

# 1 单个泛型参数

function createArray<T>(times: number, value: T): T[] {
	let result = [];
    for (let i = 0; i < times; i++) {
        result.push(value)
    }
    return result
}
// 使用的时候传入参数,才知道具体的类型
let result = createArray(5, 'abc');
console.log(result);

# 2 多个泛型参数

// 元组进行类型交换
const swap = <T, K>(tuple: [T, K]): [K, T] => {
	return [tuple[1], tuple[0]];
}
let result = swap(['a', 1]);
console.log(result);

# 3 类型别名中使用

// 在类型别名中使用泛型,由于类型别名不能被继承和实现,通常用于联合类型的声明
type TArray = <T, K>(tuple: [T, K]) => [K, T];
const getArray:TArray = <T, K>(tuple: [T, K]): [K, T] => {
    return [tuple[1], tuple[0]]
}
console.log(getArray([5, 'abc']));

# 4 接口中使用

interface IArray {
    <T, K>(tuple: [T, K]): [K, T]
}
const getArray:IArray = <T, K>(tuple: [T, K]): [K, T] => {
    return [tuple[1], tuple[0]]
}
console.log(getArray([5, 'abc']));

# 5 接口中传参

interface ICreateArray<K> {
    <T>(x: K, y: T): T[];
}
const createArray: ICreateArray<number> = <T>(times: number, value: T): Array<T> => {
    let result = [];
    for (let i = 0; i < times; i++) {
        result.push(value)
    }
    return result
}
console.log(createArray(3, 'abc'));

TIP

interface后面的类型和函数前面的类型的区别:如果放在函数前 表示使用函数的时候确定了类型,放在接口的后面 表示是使用接口的时候确定类型

# 6 泛型约束

// 泛型中必须包含某个属性
type withLen = {
	length: number
}
const computeArrayLength = <T extends withLen, K extends withLen>(arr1: T, arr2: K): number => {
    return arr1.length + arr2.length
}
console.log(computeArrayLength('123', { length: 3 }));
// 返回泛型中指定属性
const getVal = <T extends object, K extends keyof T>(obj: T, key: K) => {
   return obj[key];
}
console.log(getVal({ a: 1, b: 2 }, 'a'));

# 7 类中使用

class GetArrayMax<T> { // [1,2,3] [a,b,c]
    public arr: T[] = [];
    add(val: T) {
        this.arr.push(val)
    }
    getMax():T {
        let arr = this.arr;
        let max = arr[0];
        for (let i = 1; i < arr.length; i++) {
            arr[i] > max ? max = arr[i] : null
        }
        return max;
    }
}
// 需要在调用类的时候传入具体的类型
let arr = new GetArrayMax<number>();
arr.add(1);
arr.add(2);
arr.add(3);
let r = arr.getMax();
console.log(r);

# 8 默认参数

// 可以默认给泛型指定一个类型
interface IObj<T = string> {
    name: T
}
let name:IObj = {name:'zf'}
console.log(name.name);

# 九 类型保护

  • 主要通过JS的一些方法和TS自带的一些方法进行类型保护

# 1 typeof

function fn(val: string | number) {
	// typeof 判断类型
    if (typeof val == 'string') {
        val.match
    } else {
        val.toFixed
    }
}

# 2 instanceof

class Person {}
class Dog { }

const getInstance = (clazz: new () => Person | Dog) => {
    return new clazz
}
let r = getInstance(Person);
// instanceof 判断实例
if (r instanceof Person) {
    r // Person
} else {
    r // Dog
}

# 3 in

interface Fish {
    swiming: string
}
interface Bird {
    fly: string
}
function getType(animal: Fish | Bird) {
	// in 判断属性
    if ('swiming' in animal) {
        animal // Fish
    } else {
        animal // Bird
    }
}

# 4 可辨识字面量

interface IButton1 {
    color: 'blue' // 可辨识字面量
    class: string
}
interface IButton2 {
    color: 'green' // 可辨识字面量
    class: string
}
function getButton(button: IButton1 | IButton2) {
    if (button.color == 'blue') {
        button // IButton1
    } else {
        button // IButton2
    }
}

# 5 is

interface Fish {
    swiming: string
}
interface Bird {
    fly: string
}

function isFish(animal: Fish | Bird): animal is Fish {
    return 'swiming' in animal
}
function getAnimalType(animal: Fish | Bird) {
    if (isFish(animal)) {
        animal.swiming
    } else {
        animal.fly
    }
}

# 6 null

function getNum(val: number | null) {
	val = val || 0;
	// 明确出来是数字,一定非null
    val.toFixed 
    function inner() {
        // 内层函数不能识别,需要二次判断非null
        val?.toFixed()
    }
    inner();
}

# 7 完整性保护

// 代码的完整性保护  主要靠的是never 利用never无法到达最终结果的特性,来保证代码的完整
interface ISquare {
    kind: 'square',
    width: number
}
interface IRant {
    kind: 'rant',
    width: number,
    height: number
}
interface ICircle {
    kind: 'circle',
    r: number
}
const assert = (obj: never) => { throw new Error("err"); }
function getArea(obj: ISquare | IRant | ICircle) {
    switch (obj.kind) {
        case 'square':
            return obj.width * obj.width;
        case 'rant':
            return obj.width * obj.height;
        case 'circle':
            return
        default:
			// 完整性保护 保证代码逻辑全部覆盖到
            assert(obj);
    }
}
getArea({ kind: 'circle', r: 10 });

# 十 兼容性

  • ts的检测方式为鸭子检测,像就可以通过,一切都是为了安全考虑

# 1 基本类型兼容性

// 子类型 赋值给 父类型
type NumOrStr = number | string; // 定义大类型
let numOrStr: NumOrStr = 'abc';  // 子类型 -> 父类型
// 多条件 赋值给 少条件
type MyStr = { 
	toString(): string 
}
// 只要有tostring函数就可以通过
let str: MyStr = 'hello'; // 多的条件可以赋予给少的条件

# 2 接口兼容性

  • 只要满足接口中所需要的类型就可以兼容,即赋值对象的参数个数需要大于(全包含)被赋值的对象的参数个数
interface IVegetables {
    color: string,
    taste: string
}
let vegetables: IVegetables;
let tomato = {
    color: 'red',
    taste: 'sour',
    size: 'big'
}
 // tomato属性中已经全部包含了vegetables的属性,可以兼容
vegetables = tomato;

# 3 函数兼容性

  • 函数的参数个数兼容性,即赋值函数的参数要少于等于被赋值的函数,与接口的对象刚好相反
let sum1 = (a: string, b: string): string => a + b
let sum2 = (a: string): string => a
sum1 = sum2; // sum1定义为两个参数,sum2只传一个参数,安全可以兼容


function forEach<T>(arr: T[], cb: (item: T, index: number, arr: T[]) => void) {
    for (let i = 0; i < arr.length; i++) {
        cb(arr[i], i, arr);
    }
}
// 定义个三个参数,多传不行,少传可以兼容。类似JS的函数
forEach([1, 2, 3, 4], (item) => {
    console.log(item);
})
  • 函数的返回值兼容性,即类似接口,即赋值对象的参数个数需要大于(全包含)被赋值的对象的参数个数
type sum1 = () => { name: string };
type sum2 = () => { name: string, age: number }

let sum1!: sum1;
let sum2!: sum2;

// sum2 返回对象全部包含sum1中的属性,可以兼容
sum1 = sum2;

# 4 逆变与协变

  • 逆变与协变,是针对参数的类型做兼容性处理,TS中特有的
  • 函数的参数是逆变的可以传父类,函数的返回值是协变的可以返回子类
  • 口诀:传逆父 返协子
class Parent {
    money!: string
}
class Child extends Parent {
    house!: string
}
class Grandson extends Child {
    eat!: string
}

// 定义函数,限制类型
type Callback = (person: Child) => Child
function getFn(cb: Callback) {}

// 调用函数:传递参数可以是儿子和父亲,返回值可以是儿子和孙子
getFn((person: Parent) => new Grandson);

TIP

  1. 并集 可以用少的 赋予给多的 string | number => number、string
  2. 多的属性可以赋予给少的属性 =》 对象
  3. 函数的参数个数少的可以赋予给个数多的

# 十一 内置类型

# 1 三元条件

  • 可以使用extends关键字和三元表达式,实现条件判断
  • 如果传入的是一个联合类型,会进行条件的分发判断
interface Fish {
	name: string,
	type: '鱼'
}
interface Bird {
	name: string,
	type: '鸟'
}
interface Swiming {
	swiming: string
}
interface Sky {
	sky: string
}

type MyType<T> = T extends Bird ? Sky : Swiming;
// 传入单个不具备分发功能
type IBird = MyType<Bird>;
// 传入一个联合类型,具备分发功能:Fish extends Bird |  Bird  extends Bird
type IEnv = MyType<Fish | Bird>;

# 2 Exclude

// 排除类型:在多个类型中排除掉某几个
type Exclude<T, K> = T extends K ? never : T;
type MyExclude = Exclude<string | number | boolean, boolean | number>

# 3 Extract

// 抽取类型:在多个类型中抽取某几个
type Extract<T, K> = T extends K ? T : never;
type MyExtract = Extract<string | number | boolean, boolean | number>

# 4 NonNullable

// 非空检测:在多个类型中排除null undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type MyNonNullable = NonNullable<string | number | null | undefined>

# 5 ReturnType

// 推断函数返回值类型
function getSchool(x: string, y: number) {
	return { name: 'zf', age: 12 }
}
type ReturnType<T extends ((...args: any[]) => any)> = T extends ((...args: any[]) => infer R) ? R : any
type MyReturnType = ReturnType<typeof getSchool>;

# 6 Parameters

// 推断函数参数类型
function getSchool(x: string, y: number) {
	return { name: 'zf', age: 12 }
}
type Parameters<T extends ((...args: any[]) => any)> = T extends (...args: infer P) => any ? P : any
type MyParameters = Parameters<typeof getSchool>;

# 7 ConstructorParameters

// 推断类中构造函数的参数类型
class Person {
	constructor(name: string) { }
}
type ConstructorParameters<T extends new (...args:any[])=> any> = T extends new (...args:infer C)=> any ? C:any
type MyConstructorParameters = ConstructorParameters<typeof Person>;

# 8 InstanceType

// 推断类中实例的类型
class Person {
	constructor(name: string) { }
}
type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any
type MyInstanceType = InstanceType<typeof Person>;

# 9 Partial

// 将所有属性转化为选填属性,默认不能深度递归
interface ICompany {
    name: string,
    address: string
}
interface IPerson {
    name: string,
    age: number,
    company: ICompany
}

// 默认浅遍历
type Partial<T> = { [K in keyof T]?: T[K] }
type MyPerson = Partial<IPerson>

// 深度递归:需要嵌套继续循环
type DeepPartial<T> = {
    [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K]
}
type DeepPartialPerson = DeepPartial<IPerson>;

# 10 Required

// 将所有属性转化为必填属性   -? 去掉可选
interface IPerson {
    name?: string | undefined,
    age?: number | undefined,
}
type Required<T> = { [K in keyof T]-?: T[K] }
type MyRequired = Required<IPerson>

# 11 Readonly

// 将所有属性转化为只读属性
interface IPerson {
    name: string,
    age: number
}
type Readonly<T> = { readonly [K in keyof T]: T[K] }
type MyReadonly = Readonly<IPerson>

# 12 Pick

// Pick是从对象中挑选某些属性,extract是从类型中选择类型
interface IPerson {
    name: string,
    age: number
}
type Pick<T, K extends keyof T> = {
	[X in K]: T[X]
};
type MyPick = Pick<IPerson, 'age'>

# 13 Omit

// 忽略对象中的某些属性
interface IPerson {
    name: string,
	age: number,
	address: string
}
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>
type MyType = Omit<IPerson, 'address'>;

# 14 Record

// 记录类型
type Record<K extends keyof any, T> = { [P in K]  : T }
let person: Record<string, any> = { name: 'zf', age: 11 };


// 用Record类型表示映射类型
function map<K extends keyof any, V, X>(obj: Record<K, V>, cb: (item: V, key: K) => X):Record<K,X> {
    let result= {} as Record<K,X> ;
    for(let key in obj){
        result[key] = cb(obj[key],key)
    }
    return result
}

let r = map({ name: 'zf', age: 12 }, (item) => {
    return '$' + item
})

# 十二 自定义类型

# 1 Diff 差集

// 差集:求两个对象不同的部分
let person1 = {
    name: 'test',
    age: 10,
    address: 'address'
}
let person2: {
    address: 'address'
}
// 使用Omit忽略对象中的属性实现
type Diff<T extends object, K extends object> = Omit<T, keyof K>;
type myDiff = Diff<typeof person1, typeof person2>;

/**
 * 得到结果
 type myDiff = {
    name: string;
    age: number;
}
 */

# 2 Inter 交集

// 交集:求两个对象相同的部分
let person1 = {
    name: 'test',
    age: 10,
    address: 'address'
}
let person2: {
    address: 'address'
}
// 使用Pick从一个对象中挑取某个类型,Extract排除属性
type Inter<T extends object, K extends object> = Pick<K, Extract<keyof T, keyof K>>
type myInter = Inter<typeof person1, typeof person2>;

/**
 * 得到结果
 type myDiff = {
    address: "address";
}
 */

# 3 Merge 合并对象

  • 两个对象的合并,一般都是以后者为准, 然后再补充前者里面特有的属性
type Person1 = {
    name: string,
    age: number
}
type Person2 = {
    age: string,
    address: string
    a: string,
    b: number
}
type Compute<T> = { [K in keyof T]: T[K] };
// T里面忽略K里面相同的,然后+K里面的
type Merge<T, K> = Compute<Omit<T, keyof K> & K>;

type MergeObj = Merge<Person1, Person2>
/**
 * 得到结果
type MergeObj = {
    name: string;
    age: string;
    address: string;
    a: string;
    b: number;
}
 */

# 十三 装包和拆包

# 1 装包

  • 类似于JS的defineProperty,用于包装对象,给对象的每个属性都加一个get和set方法,例如vue3的ref
let data = {
    name: 'zf',
    age: 12
}
type Proxy<T> = {
    get(): T,
    set(value: any): void
}
type Proxify<T> = {
    [K in keyof T]: Proxy<T[K]>
}
function proxify<T>(obj: T): Proxify<T> {
    let result = {} as Proxify<T>;
    for (let key in obj) {
        let value = obj[key]
        result[key] = {
            get() {
                return value
            },
            set(newValue) {
                value = newValue
            }
        }
    }
    return result;
}
let proxyDatas = proxify(data);
console.log(proxyDatas.name.get()) // zf
proxyDatas.name.set('xxx');
console.log(proxyDatas.name.get()) // xxx

# 2 拆包

  • 将装包之后的对象还原
function unProxify<T>(obj: Proxify<T>): T {
    let result = {} as T;
    for (let key in obj) {
        let value = obj[key]
        result[key] = value.get();
    }
    return result;
}
let data2 = unProxify(proxyDatas);
console.log(data2); // {name: "xxx", age: 12}

# 十四 其他特性

# 1 命名空间

  • 命名空间可以用于组织代码,避免文件内命名冲突

  • 命名空间中导出的变量可以通过命名空间使用

  • 命名空间中的内容需要导出

  • 命名空间就是通过自执行函数来是实现的

  • 单个命名空间

export namespace zoo {
    export class Dog { eat() { console.log('zoo dog'); } }
}
export namespace home {
    export class Dog { eat() { console.log('home dog'); } }
}

let dog_of_zoo = new zoo.Dog();
dog_of_zoo.eat();
let dog_of_home = new home.Dog();
dog_of_home.eat();
  • 嵌套命名空间
export namespace Zoo {
    export class Dog { eat() { console.log('zoo dog'); } }
    export namespace bear{
        export const name = '熊'
    } 
}
console.log(Zoo.bear.name);

# 2 模块

  • 文件模块: 如果在你的 ts 文件的根级别位置含有 import 或者 export,那么它会在这个文件中创建一个本地的作用域
// a.ts导出
export default 'zf'

// index.ts导入
import name from './a'
  • 模块导入导出
import $ from 'jquery' // 只适用于 export default $

const $ = require('jquery'); // 没有声明文件可以直接使用 require语法

import * as $ from 'jquery' // 为了支持 Commonjs规范 和 AMD规范 导出时采用export = jquery

import $ = require('jquery') // export = jquery 在commonjs规范中使用

# 3 unknown

  • unknown 是any的安全类型
  • 不能访问unknown类型上的属性,不能作为函数、类来使用
  • 任何类型都可以赋值为unknown类型
let unknown: unknown;
unknown = 'zf';
unknown = 11;
  • 联合类型与unknown都是unknown类型
type UnionUnknown = unknown | null | string | number
  • 交叉类型与unknown都是其他类型
type inter = unknown & null
  • never是unknown的子类型
type isNever = never extends unknown ? true : false;
  • keyof unknown 是never
type key = keyof unknown;
  • unknown类型不能被遍历
type IMap<T> = {
    [P in keyof T]:number
}
type t = IMap<unknown>;
  • unknown类型不能和number类型进行 +运算,可以用于等或不等操作

# 4 声明文件

  • 类型声明在编译的时候都会被删除,不会影响真正的代码。目的是不重构原有的js代码,而且可以得到很好的TS支持
  • 所有类型都可以声明
  • 普通类型声明
// 声明变量
declare let age: number;
// 声明函数
declare function sum(a: string, b: string): void;
// 声明类
declare class Animal { };
// 声明枚举
declare const enum Seaons{
    Spring,
    Summer,
    Autumn,
    Winter
}
// 声明接口
declare interface Person {
    name:string,
    age:number
}
  • 声明外部引入的jQuery
// namespace表示一个全局变量包含很多子属性 , 命名空间内部不需要使用 declare 声明属性或方法
declare namespace jQuery {
    function ajax(url:string,otpions:object):void;
    namespace fn {
        function extend(obj:object):void
    }
}
jQuery.ajax('/',{});
jQuery.fn.extend({});

# 5 第三方声明文件

  • @types是一个约定的前缀,所有的第三方声明的类型库都会带有这样的前缀
  • 使用jquery时默认会查找 node_modules/@types/jquery/index.d.ts 文件
npm install @types/jquery -S