feng xiaohan
TS高级-Proxy和Reflect

Proxy

ES6 新增对象代理,相当于一个拦截器

详情见 JS 代理(Proxy)一章。

基本使用

let person = { name: "张三", age: 18 };

person.name; // 取值操作
person.name = "李四"; // 赋值操作

let personProxy = new Proxy(person, {
  // 创建一个拦截器 @1
  // 拦截取值操作
  get() {},
  // 拦截赋值操作
  set(target, key, value, receiver) {
    // @2
    return true;
  },
  // 拦截函数调用
  apply() {},
  // 拦截in操作符,
  has() {},
  // 拦截for in
  ownKeys() {},
  // 拦截new操作符
  construct() {},
  // 拦截delete操作符
  deleteProperty() {},
});

@1:拦截器接收两个参数:

  • target:需要被代理的对象(只接收引用数据类型:对象、数组、函数、set、map);
  • handler:一个通常以函数作为属性的对象,处理器对象占位符,对处理对象的描述;

@2:set 接收四个参数:

  • target:代理对象本身(此处为 person);
  • key:操作的属性(此处为 name);
  • value:操作的属性的值(此处为李四);
  • receiver:也是代理对象本身,主要是为了内部嵌套多个函数时保证上下文的正确。

Reflect

反射。和 Proxy13 个代理方法是一样的。但是它可以直接操作对象。

get()

可以通过 Reflect.get()来获取一个对象的属性:

Reflect.get(target, key, receiver);
  • target:需要被反射的对象(只接收引用数据类型:对象、数组、函数、set、map);
  • key:需要获取值的属性;
  • receiver:也是被反射的对象本身,主要是为了内部嵌套多个函数时保证上下文的正确。

例如:

let person = { name: "张三", age: 18 };

console.log(person.name); // 可以直接获取
console.log(Reflect.get(person, "name")); // 通过Reflect.get()获取

set()

可以通过 Reflect.set()来修改一个对象的属性:

Reflect.get(target, key, value, receiver);
  • target:需要被反射的对象(只接收引用数据类型:对象、数组、函数、set、map);
  • key:需要修改值的属性;
  • value:修改的值;
  • receiver:也是被反射的对象本身,主要是为了内部嵌套多个函数时保证上下文的正确。
console.log(Reflect.set(person, "name", "李四", person)); // true
console.log(person); // { name: "李四", age: 18 }

返回值:true/false

示例

let person = { name: "张三", age: 18 };

let personProxy = new Proxy(person, {
  get(target, key, receiver) {
    if (target.age <= 18) {
      return Reflect.get(target, key, receiver);
    } else {
      return "已成年";
    }
  },
});

cnosole.log(personProxy.age); // 18

实现观察者模式

const list: Set<Function> = new Set();

const autoRun = (cb: Function) => {
  if (!list.has(cb)) {
    list.add(cb);
  }
};

const observable = <T extends object>(params: T) => {
  // Proxy代理对象只支持引用数据类型,所以需要限制传入的类型
  return new Proxy(params, {
    set(target, key, value, receiver) {
      const result = Reflect.set(target, key, value, receiver);
      list.forEach((fn) => fn());
      return result;
    },
  });
};

测试观察者模式:

// 设置一个可供观察的对象
const personProxy = observable({ name: "张三", attr: "摸鱼" });

autoRun(() => {
  // 订阅函数
  console.log("有变化了");
});

personProxy.name = "李四"; // 有变化了 @1

@1:在尝试修改的时候观察对象的值时,会调用订阅的函数。修改几次数据将会执行几次订阅函数

TS高级-Partial和Pick

Partial

将一个类型中的所有属性变为可选属性

type Person = {
  name: string;
  age: number;
  sex: string;
};
type p = Partial<Person>;
// type p = {
// 		name?: string |undefined;
// 		age?: number |undefined;
// 		sex?: string |undefined;
// 		}

原理

通过keyof获取类型的联合属性,再通过in操作符(可以理解为 for in)遍历这些属性,并给每个属性上添加可选操作符:

// type key = 'name' | 'age' | 'sex'
type Partial<T> = {
  [P in keyof T]?: T[P];
};

Pick

从一个类型中选取部分属性

interface Person {
  name: string;
  age: number;
  gender: string;
}

type PersonNameAndAge = Pick<Person, "name" | "age">;
// PersonNameAndAge 的类型为:
// {
//   name: string;
//   age: number;
// }

Pick 类型接收两个参数:<被选择的类型, 被选择的类型的属性(可以使用联合属性等)>

原理

接收两个泛型,一个泛型为被选择的对象,另一个为泛型为限制在通过keyof获取类型的联合属性里的泛型,再使用in操作符循环被选择的对象将其组合起来:

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};
TS高级-infer

用于在泛型中推导类型。它通常与extends关键字一起使用,用于从一个类型中提取出另一个类型,并将其作为泛型参数的类型。

应用

  • 定义一个类型就返回一个类型

    type Type<T> = T extends Array<infer U> ? U : T;
    // infer U会充当一个占位符,去读取Array里的类型,然后再返回
    
    type A = Type<(string | number)[]>; // type A = string | number
    type B = Type<boolean>; // type B = boolean
    

    例 2:

    // 定义一个类型只能返回元组类型,其他的类型返回都会为never
    type Type<T> = T extends Array<infer U> ? U : never;
    
    type T = [string, number];
    type union = Type<T>; // 返回一个有T类型里的值组成的联合类型 type union = string | number
    

类型提取和删除

type First<T extends any[]> = T extends [infer one, infer two, infer three]
  ? one
  : [];

提取头部元素

type First<T extends any[]> = T extends [infer one, ...any[]] ? one : [];

type Arr = ["a", "b", "c"];
type a = First<Arr>; // type a = 'a'

提取尾部元素

type Last<T extends any[]> = T extends [...any[], infer last] ? last : [];

type Arr = ["a", "b", "c"];
type a = Last<Arr>; // type a = 'c'

删除尾部元素

type Pop<T extends any[]> = T extends [...infer Rest, popOne] ? Rest : [];

type Arr = ["a", "b", "c"];
type a = Last<Arr>; // type a = ['a', 'b']

删除头部元素

type Shift<T extends any[]> = T extends [shiftOne, ...infer Rest] ? Rest : [];

type Arr = ["a", "b", "c"];
type a = Shift<Arr>; // type a = ['b', 'c']

infer 递归

使用infer递归实现数组反转。

**实现思路:***定义一个ReverArr使用infer将数组的第一个取出来放到最后一个元素中,剩下的元素继续组成数组传入这个ReverArr,实现递归调用,直到再也没有剩下的元素返回。*

type ReverArr<T extends any[]> = T extends [infer First, ..infer rest] ? [...ReverArr<rest>, First] : T

type Arr = [1, 2, 3, 4]
type ArrRe = ReverArr<Arr> // type ArrRe = [4, 3, 2, 1]
TS操作符

类型操作符

keyof

获取对象中的所有键类型组成的联合类型

type Person = {
  id: number;
  name: string;
  age: number;
};

type Person1 = keyof Person; //'id' | 'name' | 'age'

JS 通过 Object.keys()获取对象的所有属性键值。

TS 封装 LocalStorage

LocalStorage 模仿 cookie 实现设置过期时间的库。

项目文件结构

- src
    - enum
        index.ts (存放枚举)
    - type
        index.ts (存放类型)
    index.ts (实现代码逻辑)
- index.html (代码测试)
- package.json
- rollup.config.js
- tsconfig.json

修改 tsconfig.json

"compilerOptions": {
    "module": "ESNext",
    "moduleResolution": "node",
    "strict": false
}

具体实现

enum/index.ts

export enum Dictionaries{
    permanent = 'permanent'; // 默认是永久的(永不过期)
    expire = '__expire__'; // 过期时间
}

type/index.ts

import { Dictionaries } from "../enum";
export type Key = string;
export type Expire = Dictionaries.permanent | number; // 过期时间为永久或一个时间戳
export interface Result<T> {
  message: string;
  value: T | null;
}
export interface Data<T> {
  value: T;
  [Dictionaries.expire]: Expire;
}
export interface StorageCls {
  get: <T>(key: Key) => void;
  set: <T>(key: Key, value: T, expire: Expire) => void;
  remove: (key: Key) => void;
  clear: () => void;
}

index.ts

import { StorageCls, Key, Expire, Data, Result } from "./type";
import { Dictionaries } from "./enum";
export class Storage implements StorageCls {
  set<T>(key: Key, value: T, expire: Expire = Dictionaries.permanent) {
    const data = {
      value, // 用户的value
      [Dictionaries.expire]: expire, // 过期时间
    };
    localStorage.setItem(key, JSON.stringify(data));
  }
  get<T>(key: Key): Result<T | null> {
    const value = localStorage.getItem(key);
    if (value) {
      const data: Data<T> = JSON.parse(value);
      const now = new Date().getTime();
      // 判断是否过期
      if (
        typeof data[Dictionaries.expire] == "number" &&
        data[Dictionaries.expire] < now
      ) {
        this.remove(key);
        return {
          message: `您的${key}已过期`,
          value: null,
        };
      } else {
        return {
          message: "成功",
          value: data.value,
        };
      }
    } else {
      return {
        message: "值无效",
        value: null,
      };
    }
  }
  remove(key: Key) {
    localStorage.removeItem(key);
  }
  clear() {
    localStorage.clear();
  }
}

打包配置

使用 rollup 进行打包:

npm install rollup typescript rollup-plugin-typescript2

rollup.config.js

import ts from "rollup-plugin-typescript2";
import path from "path";
export default {
  input: "./src/index.ts",
  output: {
    file: path.resolve(__dirname, "./dist/index.js"),
  },
  plugins: [ts()],
};
  • 新版 rollup 的打包方法:

    import ts from "rollup-plugin-typescript2";
    import path from "path";
    import { fileURLToPath } from "url";
    
    const metaUrl = fileURLToPath(import.meta.url);
    const dirName = path.dirname(metaUrl);
    export default {
      input: "./src/index.ts",
      output: {
        file: path.resolve(dirName, "./dist/index.js"),
      },
      plugins: [ts()],
    };
    

    还需要在 package.json 里type="module"

启动命令

rollup -c

测试使用

index.html

...
<body>
  <script type="module">
    import {Storage} from './dist/index.js';
    const ls = new Storage()

    ls.set('a', 123. new Date().getTime() + 5000);

    console.log(les.get('a'));
  </script>
</body>
TS 实现发布订阅者模式
// index.ts
interface Event {
  on: (name: string, fn: Function) => void;
  emit: (name: string, ...args: Array<any>) => void;
  off: (name: string, fn: Function) => void;
  once: (name: string, fn: Function) => void;
}
interface List {
  [key: string]: Array<Function>;
}
class Dispatch implements Event {
  list: List;
  constructor() {
    this.list = {};
  }
  // 注册函数
  on(name: string, fn: Function) {
    const callback = this.list[name] || [];
    callback.push(fn);
    this.list[name] = callback;
    console.log(this.list);
  }
  // 触发函数
  emit(name: string, ...args: Array<any>) {
    let eventName = this.list[name];
    if (eventName) {
      eventName.forEach((fn) => {
        fn.apply(this, args);
      });
    } else {
      console.error(`名称错误${name}`);
    }
  }
  // 删除注册的函数
  off(name: string, fn: Function) {
    let eventName = this.list[name];
    if (eventName && fn) {
      let index = eventName.findIndex((fns) => fns === fn);
      eventName.splice(index, 1);
    } else {
      console.error(`名称错误${name}`);
    }
  }
  // 只调用一次函数(设置一个临时函数,使用之后将其删除)
  once(name: string, fn: Function) {
    let del = (...args: Array<any>) => {
      fn.apply(this, args);
      this.off(name, del);
    };
    this.on(name, del);
  }
}

const sp = new Dispatch();

sp.on("post", (...args: Array<any>) => {
  console.log(args, 1); // [1,false,{name:"张三"}] 1
});
sp.once("post", (...args: Array<any>) => {
  console.log(args, "once");
});
const fn = (...args: Array<any>) => {
  console.log(args, 2); // [1,false,{name:"张三"}] 2
};
sp.on("post", fn);

sp.off("post", fn);

sp.emit("post", 1, false, { name: "张三" });
基础类型

布尔值

let isDone: boolean = false;
let isDoneNow: boolean = true;

数字

let deLiteral: number = 1;
let hexLiteral: number = 0xf00d; // 十六进制
let binaryLiteral: number = 0b1010; // 二进制
let octalLiteral: number = 0o744; // 八进制

字符串

let studentName: string = "fxh";
studentName = "dfs";

let age: number = 20;
console.log(
  `I'm ${studentName}. My age is ${age + 1}. ${age > 18 ? "成年" : "未成年"}`
);

可以使用模板字符串和三元表达式进行简单的运算

数组

  • 使用[]

    let list: number[] = [1, 2, 3];
    let list0: any[] = [1, 2, 3, "sad"];
    

    注意:定义的什么类型的数组,在后面数组里就只能使用该类型的值(除非使用any

  • 使用数组泛型

    let list: Array<number> = [1, 2, 3];
    

元组

元组(Tuple)类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同

let x: [string, number];
x = ["hello", 30];

const arr: readonly [x: number, y?: boolean] = [1]; // 可以取别名来判断是否存在
type first = (typeof arr)[0]; // type给类型起一个新的别名

注意:类型和定义的值要相匹配

  • 访问一个已知索引元素,会得到正确的类型:

    console.log(x[0]); // hello
    console.log(x[1]); // 30
    
  • 访问一个越界的元素,会使用联合类型替代

    x[3] = "world"; // 字符串可以赋值给(string | number)类型
    console.log(x[3]); // world
    

    注意:这么写编辑器和进行tsc转换时会报错,但是不影响使用转换后的js运行

枚举(enum)

enum类型是对 JavaScript 标准数据类型的一个补充。使用枚举类型可以为一组数值赋予友好的名字。

enum Color {
  Red,
  Green,
  Blue,
}
let c: Color = Color.Green;

数字枚举

默认情况下,0开始为元素编号

console.log(Color);
// { '0': 'Red', '1': 'Green', '2': 'Blue', Red: 0, Green: 1, Blue: 2 }
console.log(Color.Blue);
// 2

自定义枚举

也可以手动赋值 :

enum Color {
  Red = 1,
  Green = 2,
  Blue = 4,
}

增长枚举

当定义了第一个枚举的值时,后面的枚举值如果没有设置则会从第一个设置的值开始增长。

,枚举类型可以由枚举的值得到它的名字

enum Color {
  Red = 1,
  Green,
  Blue,
}
let colorName: string = Color[2];

console.log(colorName); // Green
console.log(Color.Red); // 1
console.log(Color.Green); // 2
console.log(Color.Blue); // 3

字符串枚举

自定义设置字符串后不再具有增长枚举的特性,设置了一个值为字符串最好把所有的值都设置为字符串:

enum Color {
  red = "red",
  green = "green",
  blue = "blue",
}
console.log(Color.red); // red

异构枚举

多种类型穿插的枚举(比如说数字和字符串):

enum Color {
  red = 0,
  green = "green",
}
console.log(Color.red); // 0

注意:异构枚举不允许在数字枚举中插入字符串枚举,因为数字枚举没有办法做递增。

enum Color {
  red,
  green = "green",
  blue, // Error
}

接口枚举

通过接口和枚举混合定义使用:

enum Color {
  red = 0,
  green = "green",
}
interface A {
  blue: Color.red;
}
let obj: A = {
  red: Color.red,
};

const 枚举

const 枚举在 ts 编译的 js 代码中,会直接将值编译出来,不会将其(Types)编译为一个对象。

const enum Types {
  sucess,
  fail,
}
let code: number = 0;
if (code === Types.sucess) {
}

上述代码使用 const 枚举编译的 js 如下:

var code = 0;
if (code === 0) {
} //Types.sucess默认数字第一位为0

普通枚举:

var Types;
(function (Types) {
    Types[Types["sucess"] = 0] ="sucess";
    Types[Types["fail"] = 0] ="fail";
})(Types || Types = {});
var code =0;
if (code === Types.sucess){}

反向映射

我们可以通过 key 去读取 value,也可以通过 value 读取 key。

只有数字类型支持,字符串类型是不支持反向映射的。

any

为那些在编程阶段还不清楚类型的变量指定一个类型,让其通过编译阶段的检查。可以定义为任何类型。

let notSure: any = true;
let notSure2: any = 123;
let notSure3: any = "fxh";

let list3: any[] = [1, true, "free"];
list3[0] = "ade";
console.log(list3);

any 类型的值可以和任何类型的值进行交换赋值(赋给别人值和被赋值都可以)。

unknown

同 any 一样,可以定义为任何类型。

let a: unknown = 1;
  • unknown 类型只能赋值给 unknown 或者是 any 类型
  • unknown 类型无法读取值的任何属性(如果给一个对象{}设置 unknown,则其对象的属性是无法读取的,方法也不能调用);但是 any 类型可以。

void

没有任何类型。

函数

当一个函数没有返回值的时候,返回类型为void

function warnUser(): void {
  console.log("This is my warning message");
}

变量

声明一个void类型的变量没有什么大用处,因为你只能为它赋予undefinednull(测试返回null时会报错,可能是因为在严格模式下)

let unusable: void = undefined;

null 和 undefined

undefinednull两者各自有自己的类型分别叫做undefinednull

let u: undefined = undefined;
let n: null = null;
  • 默认情况下nullundefined所有类型的子类型,你可以把nullundefined赋值给其他类型

    let otherValue:number = null;
    let otherValue:string = null;
    let otherValue:boolean = null;
    let otherValue:void = null;
    let otherValue:any = null;
    ....
    
  • 当你指定了--strictNullChecks标记,nullundefined只能赋值给void和它们各自。

never

表示的是那些永不存在的值的类型

注意:never 在使用联合类型时会被忽略。

  • 可以进行一些返回错误的函数:

    // 返回never的函数必须存在无法达到的终点
    function error(message: string): never {
      throw new Error(message); // 抛出错误
    }
    
    // 推断的返回值类型为never
    function fail() {
      return error("Something failed");
    }
    
    // 返回never的函数必须存在无法达到的终点
    function infiniteLoop(): never {
      while (true) {
        // 死循环
      }
    }
    

    never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never

    • 实际上 let num2:never = null;是可以被赋值为 null ,但 never 赋值不了任何值
  • 进行兜底逻辑的判断:

    type fruits = "apple" | "banana" | "pear";
    
    function eat(value: A) {
      switch (value) {
        case "apple":
          break;
        case "banana":
          break;
        case "pear":
          break;
        default:
          const error: never = value;
          break;
      }
    }
    

    这样如果修改了 fruits 的类型,使用 eat 时也能知道是哪里出了问题。

object

object表示所有非原始类型,也就是除numberstringbooleansymbolnullundefined之外的类型。

可以表示所有引用类型。

使用object类型,就可以更好的表示像Object.create这样的 API。例如:

declare function create(o: object | null): void;

create({ prop: 0 }); // OK
create(null); // OK

create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error

let a: object = "123"; // Error
let a: object = 456; // Error
let a: object = false; // Error
let a: object = []; // OK
let a: object = {}; // OK
let a: object = () => 678; // ok

Object

大写的Object类型相当于原型链最顶端的 Object。它可以定义所有基础的类型。

let a: Object = 123;
let a1: Object = "123";

对象字面量

跟 Object 定义范围类似,包含所有类型,可以理解为new Object

let a: {} = 123;
let a1: {} = "456";
let a2: {} = false;
let a3: {} = [];
let a4: {} = { name: "zs" };

对象字面量进行赋值之后,无法再对该值进行修改。

小结

类型等级

  • 顶级类型:any、unknown
  • 第二大类型:Object
  • 第三大类型:Number、String、Boolean
  • 第四大类型:number、string、boolean
  • 第五大类型:123、’张三’、false(直接赋值的类型)
  • 第六大类型:never

上层类型可以覆盖下层类型。

TS全局类型声明来源

在编写 TS 的类型定义时,可以声明全局的类型,这样在其它地方就可以直接进行使用,不用再额外的单独引入了。而这些类型定义的来源通常有以下几个:

lib

TS 所需要引用的库(一些标准的 API、浏览器的 API、DOM 相关的 API 等),相关的类型声明是放在 typescript 库下面的 lib 文件夹中,主要是.d.ts文件。

需要在tsconfig.json中配置:

{
  "compilerOptions": {
    "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"],
  }
}

如果没有 DOM,则不能使用window.document之类的对象。

type

来源于文件夹 @type 底下的文件。

可以在tsconfig.json中设置:

{
  "compilerOptions": {
    "typeRoots": [], // 声明文件目录,默认时node_modules/@types
    "types": ["node"] // 加载的声明文件包
  }
}

include/files

TS 也可以搜索include里的指定的文件,或者匹配的文件和文件夹;也可以搜索files里的文件(只能是文件)。然后编译这些文件/文件夹里定义的类型到全局中。

tsconfig.json中设置:

{
  "compilerOptions": {
    "include": [ // 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)——指定编译的文件或文件夹
        "src/**/*",
           "index.ts"
    ],
      "files": ["demo.ts"], // 指定哪些文件使用该配置(属于手动一个个指定文件)——指定某个文件进行编译(不可指定文件夹)
  }
}

小结

TS 寻找类型文件的顺序:

lib 文件夹 ——> typeRoots 文件夹 ——> include/files 文件

注意: 在 TS 中还可以创建index.d.tsglobal.d.ts,这两个文件夹下的文件不用声明(declare)就可以全局。?母鸡

tsconfig.json

通过tsc --init生成。

"compilerOptions": {
  "incremental": true, // TS编译器在第一次编译之后会生成一个存储编译信息的文件(缓存文件),第二次编译会在第一次的基础上进行增量编译,可以提高编译的速度
  "tsBuildInfoFile": "./buildFile", // 增量编译文件(缓存文件)的存储位置
  "diagnostics": true, // 打印诊断信息
  "target": "ES5", // 目标语言的版本(编译成js的版本)
  "module": "CommonJS", // 生成代码的模板标准
  "outFile": "./app.js", // 将多个相互依赖的文件生成一个文件,可以用在AMD模块中,即开启时应设置"module": "AMD",
  "lib": ["DOM", "ES2015", "ScriptHost", "ES2019.Array"], // TS需要引用的库,即声明文件,es5 默认引用dom、es5、scripthost,如需要使用es的高级版本特性,通常都需要配置,如es8的数组新特性需要引入"ES2019.Array",
  "allowJS": true, // 允许编译器编译JS,JSX文件
  "checkJs": true, // 允许在JS文件中报错,通常与allowJS一起使用
  "outDir": "./dist", // 指定输出目录
  "rootDir": "./", // 指定输出文件目录(用于输出),用于控制输出目录结构
  "declaration": true, // 生成声明文件,开启后会自动生成声明文件
  "declarationDir": "./file", // 指定生成声明文件存放目录
  "emitDeclarationOnly": true, // 只生成声明文件,而不会生成js文件
  "sourceMap": true, // 生成目标文件的sourceMap文件
  "inlineSourceMap": true, // 生成目标文件的inline SourceMap,inline SourceMap会包含在生成的js文件中
  "declarationMap": true, // 为声明文件生成sourceMap
  "typeRoots": [], // 声明文件目录,默认时node_modules/@types
  "types": [], // 加载的声明文件包
  "removeComments":true, // 删除注释
  "noEmit": true, // 不输出文件,即编译后不会生成任何js文件
  "noEmitOnError": true, // 发送错误时不输出任何文件
  "noEmitHelpers": true, // 不生成helper函数,减小体积,需要额外安装,常配合importHelpers一起使用
  "importHelpers": true, // 通过tslib引入helper函数,文件必须是模块
  "downlevelIteration": true, // 降级遍历器实现,如果目标源是es3/5,那么遍历器会有降级的实现
  "strict": true, // 开启所有严格的类型检查
  "alwaysStrict": true, // 在代码中注入'use strict'
  "noImplicitAny": true, // 不允许隐式的any类型
  "strictNullChecks": true, // 不允许把null、undefined赋值给其他类型的变量
  "strictFunctionTypes": true, // 不允许函数参数双向协变
  "strictPropertyInitialization": true, // 类的实例属性必须初始化
  "strictBindCallApply": true, // 严格的bind/call/apply检查
  "noImplicitThis": true, // 不允许this有隐式的any类型
  "noUnusedLocals": true, // 检查只声明、未使用的局部变量(只提示不报错)
  "noUnusedParameters": true, // 检查未使用的函数参数(只提示不报错)
  "noFallthroughCasesInSwitch": true, // 防止switch语句贯穿(即如果没有break语句后面不会执行)
  "noImplicitReturns": true, //每个分支都会有返回值
  "esModuleInterop": true, // 允许export=导出,由import from 导入
  "allowUmdGlobalAccess": true, // 允许在模块中全局变量的方式访问umd模块
  "moduleResolution": "node", // 模块解析策略,ts默认用node的解析策略,即相对的方式导入
  "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
  "jsxFactory": 'React.creatElment', // jsx语法解析器
  "jsx": "preserve", // jsx解析器
  "paths": { // 路径映射,相对于baseUrl
    // 如使用jq时不想使用默认版本,而需要手动指定版本,可进行如下配置
    "jquery": ["node_modules/jquery/dist/jquery.min.js"],
    "@/*": [ // 相当于取别名路径
        "./src/*"
    ]
  },
  "rootDirs": ["src","out"], // 将多个目录放在一个虚拟目录下,用于运行时,即编译后引入文件的位置可能发生变化,这也设置可以虚拟src和out在同一个目录下,不用再去改变路径也不会报错
  "listEmittedFiles": true, // 打印输出文件
  "listFiles": true// 打印编译的文件(包括引用的声明文件)
}

// 指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)——指定编译的文件或文件夹
"include": [
   "src/**/*",
   "index.ts"
],
// 指定一个排除列表(include的反向操作)——指定不编译的文件或文件夹
 "exclude": [
   "demo.ts"
],
// 指定哪些文件使用该配置(属于手动一个个指定文件)——指定某个文件进行编译(不可指定文件夹)
 "files": [
   "demo.ts"
]
Set、Map、WeakSet、WeakMap

Set

天然去重,引用类型除外。(详情方法见 JS 的 Set)

let set: Set<number> = new Set([1, 2, 3, 3, 3]);

Map

将值(value)映射到键(key),Map 的 key 可以是引用类型。(详情见 JS 的 Map)

let obj = { name: "张三" };
let map: Map<object, any> = new Map(); // Map的key的类型为object,value的类型为any

map.set(obj, "李四");
map.get(obj);

WeakMap

WeakMap 的key 只能是引用类型,而且 key 是弱引用,不会被计入垃圾回收

let obj: any = { name: "张三" }; // 1 引用类型第一次使用
let anotherObj: any = obj; // 2 第二次引用
let weakmap: WeakMap<object, any> = new WeakMap();
weakmap.set(obj, "李四"); // 2 WeakMap类型是弱引用,不会增加引用次数 @1
obj = null; // -1 释放引用
conosle.log(weakmap.get(obj)); // 李四
anotherObj = null; // -1

console.log(weakmap); // @2
conosle.log(weakmap.get(obj)); // undefind

@1:但是如果之前的引用都被删除了,weakmap.get 也不能获取到引用类型的值了。

@2:在打印 weakmap 时可能会看到里面存有的值(WeakMap {{...} => '李四'}),但是无法通过 get 取出来。这是因为 V8(Chrome 内置的 JavaScript 引擎,用于解释和执行 JavaScript 代码)的垃圾回收机制(GC)是需要一定时间的(最少为 200ms),为了避免这个时间,weakmap 是无法取到 key 的,并且不允许遍历 weakmap。

WeakSet

WeakSet里只能存引用类型,与 WeakMap 类似

let weakset = new WeakSet([obj]);