ts类型操作大全

前言

类型操作,其实就是把一种类型经过一定的逻辑,转换成另一种类型。个人认为这也是 ts 的魅力所在。本篇罗列了所有可分类的类型操作,最后则实现了一遍 ts 内置工具方法,有些方法会写和 ts 内建方法的区别及思路。

typeof

js 中本就有 typeof操作符,用来获取数据的类型(一个 js 的字符串),ts 则加强了它,可以尽量推断出该数据的类型。

注意typeof后面接的是一个 js 变量或者函数。

ts 复制代码
let obj = { name: "", age: 0 };
const objType = typeof obj; // js常量 'object'
type ObjType = typeof obj; // ts类型 { name: string, age: number }

泛型

泛型可以说是 ts 类型操作的基石。

什么是泛型

  • 泛型可以理解成 ts 中的不确定类型或者变量类型,那么包含泛型的类型,就可以叫做泛型类型。 泛型和泛型类型 之间的关系就像 函数形参和函数 的关系
  • 泛型可以应用在 ts 所有类型系统中,函数,接口,类型别名,类。
  • 在定义泛型类型时,可以通过<>传入一个变量类型,在应用泛型类型时再精确这个变量类型。
  • 泛型类型可以理解成是一个模板,配合传入具体类型,可以解决很多复用性问题。

例如,在和后端做数据交互时,后端接口返回的数据都有code, msg, data三个字段,其中 code 和 msg 的类型是固定的,data 则是根据接口含义不同,返回不同的数据。那么用泛型就可以很方便的表示

ts 复制代码
interface FetchResponse<D> {
  // 这个D只是一个变量代指,和形参一样,可以写成任意名称
  code: number;
  msg: string;
  data: D;
}

// 根据接口类型,复用泛型类型即可。
const pageData: FetchResponse<{ page: number; list: string[] }>;
/**
 *{
 * code: number;
 * msg: string;
 * data: { page: number; list: string[] };
 *}
 */

const checkData: FetchResponse<boolean>;
/**
 *{
 * code: number;
 * msg: string;
 * data: boolean;
 *}
 */

泛型的几种写法

  • 函数泛型 function fn<T>(): T {};,写函数泛型时<>必须紧跟着形参的小括号。
  • 函数变量 const fn = <T>(arg: T) => arg;,写函数泛型时<>必须紧跟着形参的小括号。
  • 类型别名泛型 type UnionType<T1, T2> = T1 | T2;
  • 接口泛型 interface I1<T> { t: T }
  • 类泛型 class Parent<T> { t: T }

泛型约束 extends

  • 就像在函数体中需要使用参数做一些操作一样,在一些条件下,我们也需要对泛型进行一些操作,例如循环,访问成员类型等,这个时候就需要对泛型进行一定的约束。
  • 如果不进行泛型约束的话,泛型会被赋予类似unknown的类型,不允许任何操作。
  • 泛型约束针对编写者,如果没写泛型约束,泛型就不可以操作;泛型约束同时也针对调用者,如果写了泛型约束,传入的参数必须符合泛型约束。
  • 泛型约束通过extends关键字实现。
ts 复制代码
/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

interface Obj {
  a: string;
  b: boolean;
  c: number;
  d: number[];
}
// 这里Pick的第二个参数,就需要约束为 Obj的key 的 联合类型的子类型
type NObj = Pick<Obj, "a" | "b" | "c">;
/*
type NObj = {
    a: string;
    b: boolean;
    c: number;
}
*/

type NObj2 = Pick<Obj, "ae" | "c">; //报错❌,ae 不是 Obj的key

type GetA<T> = T["a"]; // 报错❌,这里T可以理解成 unknown

泛型参数默认类型 =

  • 和函数的形参默认参数类似,泛型参数也可以有默认类型。语法也一样,都是使用=赋予默认值。
  • 泛型参数默认类型是针对调用者的限制 ,即当无法推导出 具有默认类型的泛型参数的类型时,该泛型将限制调用者传入的类型符合默认类型。如果没写泛型约束,对于编写者来说,哪怕参数有默认类型,仍然得当unknown用。
  • 如果一个泛型参数有默认类型,那么该参数是可选的,和 js 函数可选参数一样,其只能排在不可选参数后面。
ts 复制代码
interface A<T = string> {
  name: T;
}

const strA: A = { name: "aaa" }; // ok,A的泛型参数可以省略,会被推到为 A<string>
const numB: A = { name: 123 }; // 报错❌,这里A会被推导为A<string>
const numC: A<number> = { name: 123 }; // ok,这里A会被推导为A<number>

索引访问

和对象可以通过 key 访问 value 一样,ts 也可以通过类型的 key,访问类型的 value

ts 复制代码
type Person = { age: number; name: string; alive: boolean };
type Age = Person["age"]; // number

ts 除了可以单一索引访问,还支持联合索引访问

ts 复制代码
type Person = { age: number; name: string; alive: boolean };
type I1 = Person["age" | "name"]; //string | number

type I2 = Person[keyof Person]; // string | number | boolean

type AliveOrName = "alive" | "name";
type I3 = Person[AliveOrName]; // string | boolean

数组和元组存储的成员变量需要使用数字索引访问,它们也支持使用number来访问

ts 复制代码
type Arr = string[];
type Tuple = [string, number];

type ArrItem0 = Arr[0]; // string
type ArrItem = Arr[number]; // string

type Tuple0 = Tuple[0]; // string
type TupleValues = Tuple[number]; // string | number

ts 中的类型遍历

ts 中可以遍历的类型只有两种,对象类型联合类型。其中数组和元组算是特殊的对象,不过遍历方式和普通对象其实是一样的。

keyof(ts 独有概念)

keyof的作用,就是获取对象类型数据的 key 的联合类型( 把对象类型转换成原始类型的联合类型 )也可以理解成遍历对象类型。当然,用在原始类型上也不会报错,但是没有什么意义,因为会被当做对象处理。

ts 复制代码
type Point = { x: number; y: number };
type P = keyof Point; // 'x' | 'y'

有一个特殊需要注意的点是,当对象类型包含索引类型,且索引为 string 类型时,因为obj[0]obj['0']访问是等效的,所以其keyof操作后的结果要多联合一个number类型。

ts 复制代码
type Mapish = { [k: string]: boolean };
type M = keyof Mapish; // string | number

对象类型的值的联合类型,通过 索引访问 和 keyof 可以搞定

ts 复制代码
type ValueOf<T> = T[keyof T];
type Obj = { a: sting; b: number };
type ValueOfObj = ValueOf<Obj>; // string | number

keyof优先级高于|&

ts 复制代码
type A1 = keyof { a: string; b: number } | { a: string };
// => 'a' | 'b' | { a: string }
type A2 = keyof ({ a: string; b: number } | { a: string });
// => keyof { a: string }
// => 'a'

in

in 关键字也是 js 中原本就有的概念,可以判断某个 key 是否在一个对象中,类似instanceof在 ts 中可以用来做类型保护。当然还有for...in...遍历数组索引,这里就不展开了。

ts 复制代码
let obj = { name: "", age: 0 };
const isInObj = (key: string | number, obj: object) => key in obj;
const res1 = isInObj("name", obj); // true
const res2 = isInObj("anyKey", obj); // false

上述其实并不属于类型操作,只是 js 中in关键字的用法,在 ts 类型操作中,in关键字则是用来遍历联合类型,生成对象类型的。

  • 用在对象类型 key 的部分
  • []包裹,
  • 其左侧是一个新的类型,代指联合类型中单个的子项(eg: P)
  • 右侧则是要遍历的联合类型
  • 整个语句都可以使用这个新定义的类型 P
ts 复制代码
type Record<K extends keyof any, T> = {
  [P in K]: T;
};
type Point = { x: number; y: number };
type Obj = Record<"x" | "y", Point>; // { x: Point; y: Point; }

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};
type ObjX = Pick<Point, "x">; // { x: number; }

借助inkeyof,可以实现对象类型和联合类型的相互转化

ts 复制代码
interface Obj {
  a: string;
  b: number;
}

type ObjKeys = keyof Obj; // 转联合类型
type ObjN = {
  // 转回对象类型
  [k in ObjKeys]: Obj[k];
};

as

as关键字一开始是用来做类型断言的,其前面是 js 变量,后面则是推断的 ts 类型。

ts 复制代码
let obj = {} as { a: string };
let obj2 = {} as unknown as string;

在 ts 4.1 版本之后,as关键字可以应用在映射类型的 key 子句中,创造一个新的子句。其左侧是原始 key ,其右侧为新的 key。

借助as关键字,可以实现in关键字遍历复杂类型的联合类型

ts 复制代码
type EventConfig<Events extends { kind: string }> = {
  //原始key   as    新key
  [E in Events as E["kind"]]: (event: E) => void;
};

type SquareEvent = { kind: "square"; x: number; y: number };
type CircleEvent = { kind: "circle"; radius: number };

type Config = EventConfig<SquareEvent | CircleEvent>;
/* 
=>
{
    square: (event: SquareEvent) => void;
    circle: (event: CircleEvent) => void;
}
*/

翻转对象类型的 key 和 value

ts 复制代码
type Flip<T extends { [key: string]: string | number }> = {
  [K in keyof T as T[K]]: K;
};

type Obj = { name: "jeffery"; age: 18 };
type FlipObj = Flip<Obj>; // { jeffery: "name";18: "age"; }

对象类型转联合类型

ts 复制代码
// 处理成嵌套对象,然后通过访问 keyof T ,一次性访问所有key,从而转成联合类型。
type ToUnion<T> = {
  [K in keyof T]: { [P in K]: T[K] };
}[keyof T];
type MyObj = { a: 1; b: 2; c: 3 };
type MyUnion = ToUnion<MyObj>; //  {a: 1;} | {b: 2;} | {c: 3;}

条件类型 extends

在 ts 中,我们通过extends关键字来实现条件判断语句,extends基于继承这一层关系,在 ts 中总共有三种用途,分别是 继承,泛型约束和条件判断

ts 复制代码
type ConditionalType = SomeType extends OtherType
  ? TrueBranchType
  : FalseBranchType;

如果符合前面的继承条件SomeType extends OtherType,则新类型ConditionalTypeTrueBranchType,否则是FalseBranchType

甚至可以说 ts 通过判断两个类型的继承关系实现了条件语句

ts 复制代码
type IsChild = { a: string } extends {} ? true : false; // true

分布式条件类型

分布式条件类型是指,在 extends 关键字前面是联合类型的裸泛型参数 时,会隐式 将联合类型的每个子类型都应用到判断语句中得到一个子结果,最后再联合这些子结果得到一个新的联合类型

特别注意的是,分布式条件类型在全符合以下四种情况时才会触发

  • 仅限于泛型参数
  • 该泛型参数出现在extends关键字的前面(即只分配extends前面的内容)
  • 该泛型参数是未经处理的,裸类型
  • 该泛型参数是联合类型
ts 复制代码
type Is1 = "a" | "b" extends "a" ? true : false; // false,不是泛型参数,并不会触发分布式

type Is2<T> = keyof T extends "a" ? true : false;
type Is2Res = Is2<{ a: string } | { b: number }>; // true ,T在执行 extends前被keyof处理了(处理成了never),不是裸类型了。

type Is3<T> = T extends "a" ? true : false;
type Is3Res1 = Is3<"a" | "b">; //true | false => boolean ,符合隐式触发分布式的情况

这里有个特例就是nevernever类型被认为是空的联合类型。

ts 复制代码
type IsNever<T> = T extends never ? true : false;
type IsNever1 = IsNever<never>; // never

主要原因是,这里never作为联合类型,符合了触发分步式类型的四个条件(extends关键字前的联合类型裸泛型参数),但是,在进行分发的时候,由于 never 是空的,不可以分发,导致 T extends never语句不可执行,于是就返回了T,即never

验证是否为never直接破除其四个条件中的一个即可:

ts 复制代码
type IsNever<T> = [T] extends [never] ? true : false;
type IsNever1 = IsNever<never>; // true

infer

infer是在 ts 2.8 版本中新增的关键字,定义在extends右侧子句内,只能使用在条件判断语句的TrueBranchType中,表示根据TrueBranch推断出一个新的类型。

常见的类型解包

ts 复制代码
// 数组解包
type GetArrItem<T> = T extends (infer U)[] ? U : never;
type Item = GetArrItem<string[]>; // string

// promise解包
type GetPromiseResolve<T> = T extends Promise<infer U> ? U : never;
type PromiseResolve = GetPromiseResolve<{ a: string }>;

当推断结果有多种情况时,会自动转为联合类型,或者说 infer 会尽量推导出合适的类型。

ts 复制代码
// 获取对象的value类型
type GetObjValueType<T> = T extends { [key: string]: infer U } ? U : never;
type ObjValueType = GetObjValueType<{ a: string; 1: number; b: boolean }>;
// => string | number | boolean

// 获取 元组或者数组 值的联合类型
type GetArrValueType<T> = T extends { [key: number]: infer U } ? U : never;
type TupleValueType = GetArrValueType<[string, number]>; // string | number

结合递归,遍历处理数据,翻转元组:

ts 复制代码
type ReverseArray<T extends unknown[]> = T extends [infer First, ...infer Rest]
  ? [...ReverseArray<Rest>, First]
  : T;
type Value = ReverseArray<[1, 2, 3, 4]>; // [4,3,2,1]

结合函数参数逆变,分布式条件类型和 infer 推断的特性,把联合类型转成交叉类型:

ts 复制代码
type UnionToIntersection<T> = (
  T extends unknown ? (a: T) => unknown : never
) extends (arg: infer R) => unknown
  ? R
  : never;
type Example = UnionToIntersection<{ a: string } | { b: number }>; // { a: string } & { b: number }
  • 第一步,通过条件分布式,把联合类型转变成多个联合的函数,参数为原联合项。

    ts 复制代码
    type ToUnionFunc<U> = U extends unknown ? (a: U) => unknown : never;
    type T1 = ToUnionFunc<{ a: 1 } | { b: 2 }>;
    // ((a: {  a: 1 }) => unknown) | ((a: {  b: 2 }) => unknown)
  • 第二步,借助函数被替换或继承时参数是逆变的,与 infer 会尽量推导出合理的结果这两个特性,实现多个联合函数参数到单一函数交叉参数的目的。

    带到当前逻辑中,extends右侧的单一函数的参数需要符合是extends左侧联合函数的每个参数的(即例子中的{ a: string } { b: number })的子类型,当前推断才可以成立。

    而所有联合项的子类型,其实就是所有联合项的交叉类型(如果不理解,可以参考从集合树的角度理解 ts 不同类型之间的关系),infer可以很容易推断出这个结果。最后再把推断出来的交叉参数返回即可。

    ts 复制代码
    // 这里记得通过元组,破一下隐式触发条件分布式
    type UnionFunToIntersectionArg<UnionFunc> = [UnionFunc] extends [
      (arg: infer U) => unknown
    ]
      ? U
      : never;
    
    type T1 = ((a: { a: 1 }) => unknown) | ((a: { b: 2 }) => unknown);
    type T2 = UnionFunToIntersectionArg<T1>;
    // { a: 1 } & { b: 2 };

模板字符串类型

模版字符串类型的语法和 js 模版字符串的语法是一致的。

ts 复制代码
type World = "world";
type Greeting = `hello ${World}`; // "hello world"

当联合类型用在字符串类型的变量位置时,ts 会将所有可能的类型推断出来,并联合,其实也可以理解成另外一个分布式:

ts 复制代码
type male = "梁山伯" | "Romeo";
type female = "祝英台" | "Juliet";
type song = `${male}和${female}`;
// => "梁山伯和祝英台" | "梁山伯和Juliet" | "Romeo和祝英台" | "Romeo和Juliet"

有了字符串模板类型,ts 就突破了只能依赖原始类型的限制,开始能够创建类型。

为对象类型添加gettersetter方法:

ts 复制代码
interface Person {
  name: string;
  age: number;
  handleSay: () => string;
}
type AndGetter<T extends object> = {
  [p in keyof T as T[p] extends Function
    ? never
    : p extends string
    ? `get${Capitalize<p>}`
    : never]: () => T[p];
};
type AndSetter<T extends object> = {
  [p in keyof T as T[p] extends Function
    ? never
    : p extends string
    ? `set${Capitalize<p>}`
    : never]: (arg: T[p]) => void;
};
type NP = Person & AndGetter<Person> & AndSetter<Person>;
/* 
{
  name:string;
  age:number;
  getName: () => string;
  getAge: () => number;
  setName: (arg:string) => void;
  setAge: (arg:number) => void;
}
*/

上面其实用到了一个特殊的类型操作Capitalize,这个是 ts 编辑器内置的四个字符串操作类型之一。它们分别是:

  • 全字母大写 Uppercase<StringType>
  • 全字母小写 Lowercase<StringType>
  • 首字母大写 Capitalize<StringType>
  • 首字母小写 Uncapitalize<StringType>

映射类型

映射类型其实就是通过上述的各种操作,把一个对象类型,转换成另外一种类型。其实上面涉及对象类型转换的,都属于映射类型,由于有上面比较丰富详细的示例,它没有必要单独拎出来,再重复一遍了。

ts 内置类型操作

这些内置类型,其实很方便,尤其是在多参数的情况下,我个人很容易不记得哪个是 Key,哪个是 Type,那个是 Union,所以,在这里写的时候,我会尽量总结分个类

对象类型操作

  • Partial<Type>,将对象类型所有键都变成可选的。

    ts 复制代码
    type Partial<Type> = {
      [P in keyof Type]?: Type[P];
    };
  • Required<Type>,和Partial相反,将所有键变成必填

    ts 复制代码
    type Required<Type> = {
      [P in keyof Type]-?: Type[P];
    };
  • Readonly<Type>,将所有键变成只读

    ts 复制代码
    type Readonly<Type> = {
      readonly [P in keyof Type]: Type[P];
    };
  • Record<Keys, Value>,创建一个对象类型,键是Keys里的每一项,值是Value。这里加了一个泛型约束,就是 Keyskeyof any 的子类型。保证能被in关键字处理

    ts 复制代码
    type Record<Keys extends keyof any, Value> = {
      [k in Keys]: Value;
    };
  • Pick<Type, Keys>,从Type中挑选联合类型Keys相关的键值对,组合新的对象类型

    ts 复制代码
    type Pick<Type, Keys extends keyof Type> = {
      [P in Keys]: Type[P];
    };
  • Omit<Type, Keys>,忽略Type中的Keys相关键值对,组合新的对象类型。通过never过滤在Type中的 Keys,个人感觉叫Filter更好理解。

    ts 复制代码
    // 内置实现
    type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
    
    // 手动实现
    type Om<Type, Keys> = {
      [k in keyof Type as k extends Keys ? never : k]: Type[k];
    };
    // 示例
    interface Todo {
      title: string;
      description: string;
      completed: boolean;
      createdAt: number;
    }
    
    type TodoPreview = Om<Todo, "title" | "description">;
    //  {completed: boolean; createdAt: number;}

联合类型操作

  • Extract<UnionType, Members>,从UnionType中提取Members,和对象类型的Pick很相近,我经常搞混,总觉得叫Include比较合理

    ts 复制代码
    type Extract<UnionType, Members> = Members extends UnionType
      ? Members
      : never;
  • Exclude<UnionType, Members>,从UnionType中忽略Members,返回一个新的联合类型,其实和对象操作的Omit很相近,我也经常搞不清楚这俩。

    主要借助了条件分布式和 never 的特性。值得注意的是,其实要过滤UnionType中的每一项,所以条件语句得是UnionType extends Members

    ts 复制代码
    type Exclude<UnionType, Members> = UnionType extends Members
      ? never
      : UnionType;

函数相关操作

  • Parameters<Type>,获取函数类型的参数

    ts 复制代码
    type Parameters<Type extends Function> = Type extends (
      ...args: infer Params
    ) => unknown
      ? Params
      : never;
  • ReturnType<Type>,获取函数类型的返回值

    ts 复制代码
    type ReturnType<Type extends Function> = Type extends (
      ...args: never
    ) => infer R
      ? R
      : never;
  • ConstructorParameters<ClassType>,获取构造函数的参数类型。ClassType 是类的类型,其实在 ts 里,它本身就是构造函数,和推断函数参数比起来,只是加了new关键字而已

    ts 复制代码
    type ConstructorParameters<
      ClassType extends new (...args: never) => unknown
    > = ClassType extends new (...args: infer Params) => unknown ? Params : never;
    // 示例
    class C {
      constructor(a: number, b: string) {}
    }
    type T3 = ConstructorParameters<typeof C>; // [a: number, b: string]
  • InstanceType<ClassType>,获取示例类型,其实就是构造函数的返回值。

    ts 复制代码
    type InstanceType<ClassType extends new (args: never) => unknown> =
      ClassType extends new (...args: never) => infer R ? R : never;
    // 示例:
    class C {
      x = 0;
      y = 0;
    }
    
    type T0 = InstanceType<typeof C>; // C
  • ThisParameterType<FnType>,获取函数中 this 的类型。ts 函数中,有一个假参数 this,可以指定当前函数中的this类型,只要用infer推断出这个this加参数的类型即可。

    ts 复制代码
    type ThisParameterType<FnType extends Function> = FnType extends (
      this: infer T,
      ...args: never
    ) => unknown
      ? T
      : never;
    // 示例
    function toHex(this: Number) {
      return this.toString(16);
    }
    type thisT = ThisParameterType<typeof toHex>; // Number
  • OmitThisParameter<Type>,获取忽略 this 之后的这个函数类型。因为 this 类型需要明确指出,展开运算符无法包含 this 类型,所以使用展开运算符处理参数,即可忽略 this 的类型。

    ts 复制代码
    // 官方实现
    type OmitThisParameter<T> = unknown extends ThisParameterType<
      (this: string, age: number) => number
    >
      ? T
      : T extends (...args: infer A) => infer R
      ? (...args: A) => R
      : T;
    // 自己实现:
    type OmitThisParameter<FnType extends Function> = FnType extends (
      ...args: infer T
    ) => infer R
      ? (...args: T) => R
      : never;
    // 示例
    type F1 = OmitThisParameter<(this: string, age: number) => number>;
    type F2 = OmitThisParameter<(age: number) => number>;
    // (age: number) => number
  • ThisType<Type>,指定对象上下文中的 this 类型。记得 tsconfig 里配置"noImplicitThis": true。个人感觉它的应用场景不多,很重要的一点是因为,如果一个对象类型指定了 this 类型,那对象中所有的 key-value 类型就都得显式指定了。

    ts 复制代码
    const obj2: ThisType<{ foo1: string; bar(): void }> & any = {
      foo: "Hello",
      bar() {
        console.log(this.foo1); // this: { foo1: string; bar(): void }
      },
    };
    obj.foo; // 报错,因为只显示指定了this,没有指定其他类型。

    目前看到官网的实例很好,是通过函数参数泛型推导来解决显示类型指定的问题

    ts 复制代码
    type ObjectDescriptor<D, M> = {
      data?: D;
      methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
    };
    
    function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
      let data: object = desc.data || {};
      let methods: object = desc.methods || {};
      return { ...data, ...methods } as D & M;
    }
    
    let obj = makeObject({
      data: { x: 0, y: 0 },
      methods: {
        moveBy(dx: number, dy: number) {
          this.x += dx; // Strongly typed this
          this.y += dy; // Strongly typed this
        },
      },
    });
    
    obj.x = 10;
    obj.y = 20;
    obj.moveBy(5, 5);
  • Awaited,递归解包 promise 类型,直到最终解析的结果。

    ts 复制代码
    // 这个是ts内置实现,主要判断了thenable对象等多种情况,需要通过onfulfilled参数获取结果。
    type Awaited<T> = T extends null | undefined
      ? T // special case for `null | undefined` when not in `--strictNullChecks` mode
      : T extends object & { then(onfulfilled: infer F, ...args: infer _): any } // `await` only unwraps object types with a callable `then`. Non-object types are not unwrapped
      ? F extends (value: infer V, ...args: infer _) => any // if the argument to `then` is callable, extracts the first argument
        ? Awaited<V> // recursively unwrap the value
        : never // the argument to `then` was not callable
      : T; // non-object or non-thenable
    
    // 简版
    type SimpleAwaited<T> = T extends Promise<infer U> ? SimpleAwaited<U> : T;
    type Res = SimpleAwaited<Promise<Promise<number>>>; // number

其他

  • NonNullable<Type>,排除 null 和 undefined,主要是借助下{}类型和交叉类型的特性

    ts 复制代码
    type NonNullable<Type> = Type & {};

参考资料

相关推荐
程序员凡尘12 分钟前
完美解决 Array 方法 (map/filter/reduce) 不按预期工作 的正确解决方法,亲测有效!!!
前端·javascript·vue.js
编程零零七4 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
(⊙o⊙)~哦6 小时前
JavaScript substring() 方法
前端
无心使然云中漫步6 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者6 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_7 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋7 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120537 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢8 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写9 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js