Typescript入门篇《一》

533 阅读21分钟

最近使用vue3+typescript开发的新项目,让我对TypeScript有个一个更新的认识。TypeScript 发展至今,已经成为大型项目的标配,其提供的静态类型系统,大大增强了代码的可读性以及可维护性: 能够更早的发现代码中的错误、支持JavaScript语言的最新特性以及相同的语法和语义、跨平台。本系列博客会将我对TypeScript学习做一个回顾和总结。

基础语法

可选链运算符

  • 可选静态属性访问 obj?.prop
//当obj的值为undefined或null,则表达式返回undefined,否则返回表达式obj.prop的值
const obj1 = {a:1,b:2}
const obj2 = null
console.log( obj1?.a )   //1
console.log( obj2?.a )  //undefined
  • 可选的计算属性访问 obj?.[expr]
//当obj的值为undefined或null,则表达式返回undefined,否则返回表达式obj.prop的值
var obj1 = {a:1,b:2}
var obj2 = null
let key = "a"
console.log( obj1?.[key] )  //1
console.log( obj2?.[key] ) //undefined

  • 可选的函数调用或方法调用 fn?.()
//当fn的值为undefined或null,那么表达式的求值结果为undefined;否则,表达式的求值结果为fn()。
var func1 = function(id){return id}
var func2 = null

console.log( func1?.(1) )  //1
console.log( func2?.(2) )  //undefined
  • 空值合并运算符 a??b
//当左侧a的值为undefined或null,则返回右侧操作数b,否则返回a
null??1   //1
1??2  //1

注释

  • 单行注释

单行注释使用双斜线“//”来表示,并且不允许换行。

  • 多行注释

多行注释以“/”符号作为开始并以“/”符号作为结束。

  • 区域注释

区域注释以“//#region”符号作为开始并以“//#endregion”符号作为结束。“区域描述”用于描述该折叠区域,当代码被折叠起来时,该描述信息会显示出来。

//单行注释

/*
多行注释
*/

// 该注释只是实现该区域内代码折叠功能,并非注释代码

//#region 区域描述
 let add: addType = function (x, y) {
  return x + y
}
//#endregion

类型检查

类型检查是验证程序中类型约束是否正确的过程。类型检查既可以在程序编译时进行,即静态类型检查;也可以在程序运行时进行,即动态类型检查。TypeScript支持静态类型检查,JavaScript支持动态类型检查。

原始类型

boolean:表示两个逻辑值:true和false。

 const a: boolean = true;

string : 表示采用Unicode UTF-16编码格式存储的字符序列。

const a: string = "a";

number : 表示采用双精度64位二进制浮点数格式存储的数字。

const a: number = 1;

bigint : 表示任意精度的整数。

const a: bingint = 10n;  //十进制整数

symbol 与 unique synbol : 表示任意的symbol值

  • symbol类型不存在字面量形式( 字面量标识一个固定值 )
  • "unique symbol" 类型主要用作接口、类等类型中的可计算属性名
  • Symbol值与声明它的标识符绑定一起,并通过绑定改symbol值的标识符标识"symbol字面量";需确保symbol值与标识符之间绑定关系不可变;因此typescript 只允许使用const声明或者readonly属性声明来定义"unique symbol"类型的值
  • "unique symbol"类型的值只允许使用"Symbol()"函数或"Symbol.for()"方法的返回值进行初始化
  • 相同的参数调用"Symbol.for()"Symbol值,由于设计的局限性,typescript无法识别,会当初是不同的symbol值,开发时需注意
const s0:symbol = Symbol();

undefined、null、void

  • 非严格模式下, undefined、null可复制给其他类型;严格模式下,undefined只能赋值给undefined类型,null只能赋值给null类型
  • viod 标识某个值不存在,用于函数的返回值类型;非严格模式下,可将undefined和null 赋值给viod类型;严格模式下,函数返回只允许将undefined赋值给viod类型

顶端类型 any、unknown

类型系统中,顶端类型是所有其他类型的父类型

  • 同为顶端类型;任意类型都可以赋值给any 和 unknown类型
  • any类型赋值给任意类型,不包括never类型,而unknown类型更为安全,只允许赋值给any类型和unknown类型
  • any类型上允许任意的操作而不会产生编译错误;unknown则必须将其细化为某种集体类型,否则产生编译错误
  • 如果一个值无法自动推断出类型,会默认为any类型,非严格模式下,允许发生隐式的any类型转换,严格模式下如果值无法自动推断出类型,发生隐式的any类型转换,会产生编译错误
  • 建议减少顶端类型的使用,确实需要使用优先使用unknown,更为安全

尾端类型 never

尾端类型是所有其他类型的子类型,尾端类型不包含任何值;never类型允许赋值给任何类型;所有其他类型不能赋值给never类型

应用场景

  • 作为函数返回值类型,标识该函数无法返回一个值(函数抛异常或死循环)
  • 条件类型中使用never类型帮助完成一些类型运算
  • 类型推断发现已无可用类型,推断结果为nerver类型

object、Object、空类型 {}

  • object 表示非原始类型;原始类型为string、boolean、number、bigint、symbol、null 和 undefined。
let a: object = [];
let b: object = {};
let c: object = {
  toString() {
    return 123;
  },
};
let d: object = 1; //编译出错
  • Object 表示Object 类的实例的类型,定义了 Object.prototype 原型对象上的属性,存在原型对象的元素均支持
// Object是一个对象,但是是包含了js原始的所有公用的功能,可自己去了解TypeScript源码
interface Object {
  /** The initial value of Object.prototype.constructor is the standard built-in Object constructor. */
  constructor: Function;
  /** Returns a string representation of an object. */
  toString(): string;
  ...
}
let a: Object = [];
let b: Object = {};
let c: Object = {
  //编译出错 Type '() => number' is not assignable to type '() => string'.
  toString() {
    return 123;
  },
};
let d: Object = "a"; //字符串也存在原型对象
  • 空类型 {} 顶端类型,支持除了null和undefined 的任意类型
let a: {} = [];
let b: {} = {};
let c: {} = 1;
let d: {} = "hello";
let e: {} = null;

类型推论

如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型;如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:

联合类型 (expr&expr)

  • 联合类型使用 | 分隔每个类型
  • 当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,只能访问此联合类型的所有类型里共有的属性或方法
interface IPerson {
  id: string;
  age: number;
}

interface IWorker {
  companyId: string;
}
type exprA = IPerson | IWorker

//联合类型可为联合类型中的某个类型,也可以合并后的类型
let objA:exprA = {id: "1",age:24}
let objB:exprA = {companyId: "1"}
let objC:exprA = {id: "1",age:24,companyId: "1"}

交叉类型 (expr&expr)

交叉类型在逻辑上与联合类型时互补的,联合类型标识一个值的类型为多种类型之一,而交叉类型则标识一个值同时属于多种类型

interface IPerson {
  id: string;
  age: number;
}

interface IWorker {
  companyId: string;
}

type exprB = IPerson & IWorker

let objA:exprB = {id: "1",age:24}   //编译错误 Type '{ id: string; age: number; }' is not assignable to type 'exprB'.
  Property 'companyId' is missing in type '{ id: string; age: number; }' but required in type 'IWorker'.
  
let objC:exprB = {id: "1",age:24,companyId: "1"}  //编译成功

类型断言

类型断言(Type Assertion)可以用来手动指定一个值的类型。

语法

  • “尖括号” 语法 expr
  • as 语法
const value: string | number = 'aa'
let len = (value as string).length
let len1 = (<string>value).length

类型断言的约束

  • 联合类型可以被断言为其中一个类型
  • 类型断言允许将一个类型转换为更为精确的类型或者更为宽泛的类型(即要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 即可)
  • 某些情况下。两个复杂类型进行断言,如果出现了没有识别正确类型,导致错误拒绝了类型断言,可先将类型断言为顶端类型unknown或any(unknown更安全),在断言为目标类型

接口

接口各个类型成员可用逗号、也可用分号分隔

接口类型类型成员

属性接口 {ProtertyName :Type}

定义对象类型中属性成员的名称和类型;ProtertyName可为标识符、字符串、数字和可计算属性,Type标识该属性类型

  • 赋值的时候,变量的形状必须和接口的形状保持一致
  • 如果想设为可选属性,加?
interface Person {
    name: string;
    age?: number;  //加个问号,接口属性可传可不传,不传默认是undefined。
}
  • 一旦定义了任意属性(key为string类型),那么确定属性和可选属性的类型都必须是它的类型的子集;一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型
  • 两种任意类型签名并存时,number 类型的签名指定的值类型必须是 string 类型的签名指定的值类型的子集
interface Person {
    name: string;
    age?: number;
    [propName: string]: string | number | undefined; //可选属性不传时,类型默认undefined;因此该接口属性包括string、number、undefined三种类型
    
}
//两种任意类型签名并存时,number 类型的签名指定的值类型必须是 string 类型的签名指定的值类型的子集
interface Person1 {
    name: string;
    age?: number;
    [propName: string]: string | number | undefined; 
    [propName: number]: string | number |boolean |undefined;  //编译错误 Numeric index type 'string | number | boolean | undefined' is not assignable to string index type 'string | number | undefined'.
}

interface Person2 {
    name: string;
    age?: number;
    [propName: string]: string | number | undefined; 
    [propName: number]: string | number;  //编译成功 
}



  • 希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性
//当第一次给对象赋值之后,就不允许给只读属性赋值了
interface Person {
    readonly id: number;
    name: string;
    age?: number;
}
函数类型接口

对方法传入的参数类型,以及返回值类型进行约束,可批量进行约束;但只对传入的参数的类型和参数的个数进行约束,不对参数名称进行约束

  • 函数调用定义 (ParameterList):Type;参考函数- 调用类型
interface A{
    (type:number):number,
}
  • 构造签名 new(ParameterList):Type;参考函数- 构造类型
interface A{
    new(age:number):object
}
  • 方法签名 ProtertyName(paramterList):Type; 是声明函数类型的属性成员简写;ProtertyName可为标识符、字符串、数字和可计算属性,Type为函数返回类型
interface A{
    search(x:string):string    //方法签名
} 

let funcObj:A = {
    search : function( x:string ){
        return x
    }
}

funcObj.search("aaa")




//若接口中包括多个名字相同但参数列表不同的方法签名成员,则表示该方法为重载方法
interface A{
    f():number;
    f(x:boolean):boolean;
    f(x:string,y:string):string
}

可索引接口
  • 字符串索引接口 [IndexName:string]:Type
  • 数值索引接口 [IndexName:number]:Type
  • 如果同时存在字符串索引签名喝数值索引签名,则数值索引接口的类型必须能够赋值给字符串索引的类型
//数值索引类型为string,无法赋值给字符串索引接口类型;编译报错 “Numeric index type 'string' is not assignable to string index type 'number'.”
interface A {
  [prop:string]:number;
  [prop:number]:string;
}

//正确写法
interface A {
  [prop:string]:number | string;
  [prop:number]:string;
}

枚举

枚举表示一组有限元素的集合,并通过枚举成员名来引用集合中的元素; TypeScript支持数字的和基于字符串的枚举。

注意 : 未手动赋值的枚举项与手动赋值的重复了,TypeScript是不会察觉到这一点,编译时会被覆盖,因此尽量避免该情况发生

数值型枚举

  • 数值型枚举可自增长,若第一个枚举成员没有定义初始值,则默认0,紧邻后的枚举成员没有定义,默认加1
  • 数值型枚举,可通过枚举成员获取枚举成员值,也可反向映射,通过枚举成员值获取枚举成员名
  • 数值枚举是number类型,允许将数值型枚举类型赋值给number类型;number类型也能赋值给枚举类型

enum Direction{
  Up,   //未定义初始值,默认0
  Down, //依据前成员的值递增1,值为1
  Left = 5,   //设置初始化值 5
  Right   //依据前成员的值递增1,值为6
}
/********** 编译结果 **************/
"use strict";
var Direction;
(function (Direction) {
    Direction[Direction["Up"] = 0] = "Up";
    Direction[Direction["Down"] = 1] = "Down";
    Direction[Direction["Left"] = 2] = "Left";
    Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
  
/********** 映射 **************/
console.log(Direction.Up);  // 获取Up成员值1
console.log( Direction[0] );  //获取枚举成员 Up

/********** 赋值 **************/
const d1:Direction = Direction.Up;  //允许将数值型枚举类型赋值给number类型
const d2:Direction = 0;  //number类型也能赋值给枚举类型
const d3:Direction = 10;  //number类型的值不在枚举成员值列表中也不会产生编译错误

/********** 遍历 **************/
Object.keys(Direction).forEach(key => {
  console.log(key, Direction[key])
})

//打印结果
 0 Up
1 Down
5 Left
6 Right
Up 0
Down 1
Left 5
Right 6



字符串枚举

  • 字符串枚举成员没有自增长行为
  • 字符串枚举可通过枚举成员获取枚举成员值,但不能反向映射
  • 字符串枚举成员必须使用字符串字面量或另一个字符串枚举成员来初始化
  • 字符串枚举是string类型子类型,允许将字符串枚举类型赋值给string;但不允许将string类型赋值给字符串枚举类型
enum Direction{
  Up = "Up",   
  Down = "Down",
  Left = "Left", 
  Right = "Right"
}

/********** 编译结果 **************/
"use strict";
var Direction;
(function (Direction) {
    Direction["Up"] = "Up";
    Direction["Down"] = "Down";
    Direction["Left"] = "Left";
    Direction["Right"] = "Right";
})(Direction || (Direction = {}));

/********** 赋值 **************/
const d1:Direction = Direction.Up;  //允许将字符串型枚举类型赋值给string类型
const d2:Direction = "Up";  //编译错误 Type '"Up"' is not assignable to type 'Direction'

异构型枚举 (不推荐使用)

  • 异构枚举若第一个枚举成员没有定义初始值,则默认0,紧邻后的枚举成员没有定义,默认加1;
  • 若枚举成员没有定义初始值并且与之紧邻的前一个枚举成员值是数值型常量,那么该枚举成员是常量枚举成员并且初始值为紧邻的前一个枚举成员值加1。如果紧邻的前一个枚举成员的值不是数值型常量,那么将产生错误
  • 异构型枚举可通过枚举成员获取枚举成员值,但不能反向映射
  • 枚举中可以同时定义数值型枚举和字符串型枚举成员
  • 不允许使用计算的值作为枚举成员初始值
  • 必须为紧跟在字符串枚举成员之后的数值型枚举成员指定一个初始值

常量枚举成员与计算枚举成员

  • 常量枚举表达式可以是数字字面量、字符串字面量和不包含替换值的模板字面量。
  • 常量枚举表达式可以是对前面定义的常量枚举成员的引用。
  • 常量枚举表达式可以是用分组运算符包围起来的常量枚举表达式。
  • 常量枚举表达式中可以使用一元运算符“+”“-”“~”,操作数必须为常量枚举表达式。
  • 常量枚举表达式中可以使用二元运算符“+”“-”“*”“**”“/”“%”“<<”“>>”“>>>”“&”“|”“^”,两个操作数必须为常量枚举表达式。
  • 除常量枚举成员之外的其他枚举成员都属于计算枚举成员

const 枚举类型

const枚举类型在编译阶段直接替换成对应的常量,不生成额外的反向映射代码

const enum Direction{
  Up = "Up",   
  Down = "Down",
  Left = "Left", 
  Right = "Right"
}

console.log( Direction.Up )

//编译结果
"use strict";
console.log("Up" /* Up */);

数组

数组表示一组有序元素的集合

简单数组类型表示 (类型+方括号)

  • 单一原始类型或类型引用数组 (TElement[])
//例子
let arrA: number[] = [1, 1, 2];  // 数值型数组
interface personRaw {
 sex : number;
 name : string
}
let arrA: personRaw[] = [{sex:0,name:"张三"}];  // 单一类型引用数组

  • 数组类型为复合类型时,需要在数组元素类型上使用分组运算符()
//例子
let arrA: (string|number)[] = [1, "aa" 2];  // 复合类型数组

泛型数组表示法 ( Array )

//例子
interface personRaw {
 sex : number;
 name : string
}
let arrA: <number>[] = [1, 1, 2];
let arrB: <string|number>[] = [1, "aa", 2];
let arrC: <personRaw>[] = [{sex:0,name:"张三"}];

用接口表示数组/类数组

一般使用接口表示数组的场景是来描述类数组

//接口表示数组
interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

//类数组
interface IArguments {
    [index: number]: any;
    length: number;
    callee: Function;
}

只读数组

语法
  • 内置类型 : ReadonlyArrray (T表示数组元素类型)
  • 修饰符 : readonly (readonly置于数组类型之前;使用readonly修饰符不允许与泛型数组类型表示法一起使用)
  • 工具类型 : Readonly (T是数组类型,可将类型参数T的所有属性转换为只读属性)
// 内置类型 ReadonlyArrray<T> 
const arrA:ReadonlyArray<number> = [1,2,3]
const arrB:ReadonlyArray<number|string> = [1,"a",3]

//修饰符 readonly
const arrA:readonly number[] = [1,2,3];
const arrB:readonly Array<number> = [1,2,3] // 编译错误;readonly不允许与泛型数组类型表示法一起使用

//工具类型 Readonly<T> 
const arrA:Readonly<number[]> = [1,2,3]

//Readonly<T> 定义写法
type Readonly<T> = {
  readonly [P in keyof T]: T[P]
}

注意 : 允许将常规数组类型赋值给只读数组类型,不允许将只读数组类型赋值给常规数组类型

总结

  • 在定义简单数组类型时,如数组元素为单一原始类型或类型引用,使用简单数组类型表示法更加清晰和简洁,复合型数组时,使用泛型或更清晰简洁
  • 当访问数组中不存在元素时会返回undifined的值,ts类型系统无法推断是否存在数组访问越界,因此还是会得到声明的数组元素类型、
let arrA: <number>[] = [1, 1, 2];
let keyA:number = arrA[100]  //此时获取的值为undifined,但编译通过

元组

语法 : [T0,T1,...Tn]

  • 元组(Tuple) 表示有限元素构成的有序列表,元组类型是数组类型的子类型
  • 元组是长度固定的数组,并且每个元素都有确定的类型
  • 数组中元素的属类与元组类型定义中的数量保持一致
  • 元组类型允许赋值给常规数组类型和只读数组类型,但只读元组类型只允许赋值给只读数组类型

可选元素、剩余元素

  • 可选元素语法 : T1?
  • 剩余元素 : ...Tn
  • 元组中同时存在可选元素和必选元素,那么可选元素必须位于必选元素之后,剩余元素在最后
  • 当元组存在剩余元素,则元素数量开放

只读元组

同数组

函数

参数类型

固定参数类型
  • 参数需明确指定参数类型;参数没有明确指定参数类型,并且编译器无法推断参数类型,默认any,严格模式下会产生编译错误
  • 不允许输入多余的(或者少于要求的)参数
function add(x:number, y:number) {
   return x+y
}
add(1)  //少于要求参数,编译报错 : Expected 2 arguments, but got 1
add(1,2,3) //多于要求参数,编译报错 : Expected 2 arguments, but got 3.
add(1,2)  //编译成功

可选参数类型
  • 函数形式参数名后添加?,参数变为可数参数
  • 当可选参数未传值,默认undifined
  • 可选参数必须位于固定参数之后
function add(x:number, y?:number) {
   return x+(y??0)
}
add(1)  //编译成功
add(1,2,3) //多于要求参数,编译报错 :Expected 1-2 arguments, but got 3.
add(1,2)  //编译成功

//可选参数位于固定参数之前,编译错误 : A required parameter cannot follow an optional parameter.
function add1(x?:number, y:number) {
   return x+(y??0)
}
默认参数类型
  • 同一个函数参数不允许同时声明为可选参数和默认参数
  • 默认参数位于固定参数之前,则默认参数将会被是为固定参数;位于固定参数之后,将被视为可选参数
//编译错误 : Parameter cannot have question mark and initializer.
function add(x:number=0, y?:number=0) {
   return x+y
}


//默认参数位于固定参数之后,视为可选参数
function add(x:number, y:number=0) {
   return x+y

}
add(1)  //编译成功
add(1,2)  //编译成功

//默认参数位于固定参数之前,视为固定参数
function add1(x:number=0, y:number) {
   return x+y

}
add1(1)  //编译失败 :Expected 2 arguments, but got 1.
add1(1,2) //编译成功
剩余参数类型
  • 函数传参同时存在可选元素和必选元素,那么可选参数必须位于固定参数之后,剩余参数在最后
  • 剩余参数语法 ...args:TElement[]
function add(x:number, y:number,...z:number[]) {
   return x+y

}
add(1,2) //编译成功
add(1,2,3) //编译成功
add(1,2,3,4) //编译成功

//当剩余参数定义为元组类型是,参数的数量以元组类型为主
function add1(x:number, y:number,...z:[number,number]) {
   return x+y

}
add1(1,2) //编译错误 : Expected 4 arguments, but got 2.
add1(1,2,3) //编译错误 : Expected 4 arguments, but got 3
add1(1,2,3,4) //编译成功

解构参数类型
  • 数组参数类型解构
function f0([x, y]: [number, number]) {}
  • 对象参数类型解构
function f1({ x, y }: { x: number; y: number }) {}

返回值类型

  • 大多数情况,ts能根据函数体内return语句推断返回值类型,可省略返回值类型
  • void类型可作为函数的返回值类型 ,意味着函数只能返回undifined,未开启strictNullChecks编译选项页允许返回null值
let f:()=>void = function(){}

函数表达式

  • 函数表达式可用函数类型字面量定义,也可用对象类型字面量来定义,函数类型字面量的优点是简洁,而对象类型字面量定义具有更强的类型表达能力

  • 函数类型字面量是定义函数类型方法之一,能够指定函数的参数类型、返回值类型

  • 函数本质上是一个对象,但特殊的地方在于函数是可调用的对象,可使用对象类型标识函数类型;对象类型标识函数可增加对函数的其他描述

调用类型
  • 字面量定义 ( ParameterList ) =>Type
let f:()=>void = function(){}
let f1:(x:number,y:number) => number = function(x,y){
    return x+y
}

  • 调用签名(接口定义) {( ParameterList ):Type
interface addType {
  (x: number, y: number): number
}
let add: addType = function (x, y) {
  return x + y
}

//增加函数的相关描述时,使用接口定义能够准确表达
interface addType1 {
  (x: number, y: number): number;
  version : string
}
function add1(x:number,y:number){
    return x + y
}
add1.version = "1.0"

let add1Func:addType1 = add1
构造类型

定义了该对象类型表示的构造函数在使用new运算符调用时的参数列表喝返回值类型;new是运算符关键字,ParameterList表示构造函数形式参数列

  • 字面量定义 new(ParameterList) => Type
interface ErrorRaw {
    name: string;
    message: string;
    stack?: string;
}
let errFunc:new(message?: string)=>ErrorRaw =  Error
let errValue: ErrorRaw =  new errFunc()

  • 构造签名(接口定义) {new(ParameterList):Type}
interface ErrorRaw {
    name: string;
    message: string;
    stack?: string;
}

interface ErrorConstructor {
    new(message?: string): ErrorRaw;
}

let errFunc: ErrorConstructor =  Error
let errValue: ErrorRaw =  new errFunc()

重载函数
特点
  • 重载函数是指一个函数同时用于多个同类的函数签名,当使用不同数量和类型的参数调用重载函数时,可以执行不同的函数实现代码
  • 每一个重载函数值允许有一个函数实现,开发需要在这个唯一的函数实现中实现所有函数重载的功能,并且它必须位于所有函数重载语句之后,否则会编译错误
函数重载解析顺序
  • 函数实际参数的数量不少于函数重载中定义的必选参数的数量。
  • 函数实际参数的数量不多于函数重载中定义的参数的数量。
  • 每个实际参数的类型能够赋值给函数重载定义中对应形式参数的类型。
  • 函数实现的函数签名不属于重载函数的调用签名之一
  • 调用签名的书写顺序,要确保更精确的调用签名位于更靠前的位置
//函数重载定义
function test(id: number): string | undefined;   //函数调用签名
function test(type: string): string[];   //函数调用签名
function test(query: any): any {    //函数实现    函数实现中的函数签名不属于重载函数的调用签名之一
  if (typeof query === "number") {
    return query+""
  } else if( typeof query === "string" ) {
    return [query]
  }else{
      return ""
  }
}

test(true)  //编译错误,该类型不符合重载函数两个调用签名的参数类型:  Argument of type 'boolean' is not assignable to parameter of type 'number'.
test(1)  //编译成功
test("a") //编译成功


// 接口重载定义;需要注意的是,接口模式返回参数是需一致或兼容关系

interface funcType {
    (id: number): string | number;   
    (type: string): string;
}
let testFunc: funcType = function (query) {
    if (typeof query === "number") {
    return query+""
  } else if( typeof query === "string" ) {
    return query
  }else{
    return ""
  }
}
testFunc(true) //  Argument of type 'boolean' is not assignable to parameter of type 'number'.
testFunc(1)
testFunc("a")

//构造函数重载

interface ErrorRaw {
    name: string;
    message: string;
    stack?: string;
}

interface ErrorConstructor {
    new(message?: string): ErrorRaw;
    (message?: string): ErrorRaw;
    readonly prototype: ErrorRaw;
}

let errFunc: ErrorConstructor =  Error  //Error函数
let errValue: ErrorRaw =  new errFunc() 
let errValue1: ErrorRaw =  errFunc()