ts基础知识
1. any 类型,unknown 类型,never 类型
TypeScript 有两个“顶层类型”(any和unknown),但是“底层类型”只有never唯一一个
1、any
1.1 基本含义
any 类型表示没有任何限制,该类型的变量可以赋予任意类型的值。变量类型一旦设为any,TypeScript 实际上会关闭这个变量的类型检查。即使有明显的类型错误,只要句法正确,都不会报错。
let x:any;x = 1; // 正确
x = 'foo'; // 正确
x = true; // 正确
TypeScript 认为,只要开发者使用了any类型,就表示开发者想要自己来处理这些代码,所以就不对any类型进行任何限制,怎么使用都可以。
从集合论的角度看,any类型可以看成是所有其他类型的全集,包含了一切可能的类型。TypeScript 将这种类型称为“顶层类型”(top type),意为涵盖了所有下层。
1.1.1 类型推断问题
对于开发者没有指定类型、TypeScript 必须自己推断类型的那些变量,如果无法推断出类型,TypeScript 就会认为该变量的类型是any。
function add(x, y) {return x + y;
}add(1, [1, 2, 3]) // 不报错
上面示例中,函数add()的参数变量x和y,都没有足够的信息,TypeScript 无法推断出它们的类型,就会认为这两个变量和函数返回值的类型都是any。以至于后面就不再对函数add()进行类型检查了,怎么用都可以。这显然是很糟糕的情况,所以对于那些类型不明显的变量,一定要显式声明类型,防止被推断为any。
当声明的时候没有赋值,没给类型也不会报错,所以在声明的同时要赋值,不然会存在安全隐患
1.1.2污染问题
ny类型除了关闭类型检查,还有一个很大的问题,就是它会“污染”其他变量。它可以赋值给其他任何类型的变量(因为没有类型检查),导致其他变量出错
let x:any = 'hello';
let y:number;y = x; // 不报错y * 123 // 不报错
y.toFixed() // 不报错
上面示例中,变量x的类型是any,实际的值是一个字符串。变量y的类型是number,表示这是一个数值变量,但是它被赋值为x,这时并不会报错。然后,变量y继续进行各种数值运算,TypeScript 也检查不出错误,问题就这样留到运行时才会暴露。
污染其他具有正确类型的变量,把错误留到运行时,这就是不宜使用any类型的另一个主要原因。
2.unknown
与any含义相同,表示类型不确定,可能是任意类型,但是它的使用有一些限制,不像any那样自由,可以视为严格版的any
unknown类型跟any类型的不同之处在于,它不能直接使用。主要有以下几个限制:
1.unknown类型的变量,不能直接赋值给其他类型的变量(除了any类型和unknown类型)
let v:unknown = 123;let v1:boolean = v; // 报错
let v2:number = v; // 报错
2. 不能直接调用unknown
类型变量的方法和属性。
let v1:unknown = { foo: 123 };
v1.foo // 报错let v2:unknown = 'hello';
v2.trim() // 报错let v3:unknown = (n = 0) => n + 1;
v3() // 报错
3. unknown
类型变量能够进行的运算是有限的,只能进行比较运算(运算符==
、===
、!=
、!==
、||
、&&
、?
)、取反运算(运算符!
)、typeof
运算符和instanceof
运算符这几种,其他运算都会报错
let a:unknown = 1;a + 1 // 报错
a === 1 // 正确
4. 经过“类型缩小”,unknown
类型变量才可以使用。所谓“类型缩小”,就是缩小unknown
变量的类型范围,确保不会出错。
let a:unknown = 1;if (typeof a === 'number') {let r = a + 10; // 正确
}
unknown可以看作是更安全的any。一般来说,凡是需要设为any类型的地方,通常都应该优先考虑设为unknown类型。也视为所有其他类型(除了any)的全集,所以它和any一样,也属于 TypeScript 的顶层类型。
3.never
由于不存在任何属于“空类型”的值,所以该类型被称为never,即不可能有这样的值。
never类型的使用场景,主要是在一些类型运算之中,保证类型运算的完整性。另外,不可能返回值的函数,返回值的类型就可以写成never
如果一个变量可能有多种类型(即联合类型),通常需要使用分支处理每一种类型。这时,处理所有可能的类型之后,剩余的情况就属于never类型。
function fn(x:string|number) {if (typeof x === 'string') {// ...} else if (typeof x === 'number') {// ...} else {x; // never 类型}
}
never
类型的一个重要特点是,可以赋值给任意其他类型。
function f():never {throw new Error('Error');
}let v1:number = f(); // 不报错
let v2:string = f(); // 不报错
let v3:boolean = f(); // 不报错
上面示例中,函数f()
会抛错,所以返回值类型可以写成never
,即不可能返回任何值。各种其他类型的变量都可以赋值为f()
的运行结果(never
类型)
2.类型系统
1.基本类型(继承js基本数据类型)
boolean
string
number
bigint
symbol
object
undefined
null
注意:
1.上面所有类型的名称都是小写字母,首字母大写的Number、String、Boolean等在 JavaScript 语言中都是内置对象,而不是类型名称
2.如果没有声明类型的变量,被赋值为undefined或null,它们的类型会被推断为any,如果希望避免这种情况,则需要打开编译选项strictNullChecks
2.包装对象类型
TypeScript 对五种原始类型分别提供了大写和小写两种类型:
Boolean 和 boolean
String 和 string
Number 和 number
BigInt 和 bigint
Symbol 和 symbol
大写类型同时包含包装对象和字面量两种情况,小写类型只包含字面量,不包含包装对象
const s1:String = 'hello'; // 正确
const s2:String = new String('hello'); // 正确const s3:string = 'hello'; // 正确
const s4:string = new String('hello'); // 报错
String类型可以赋值为字符串的字面量,也可以赋值为包装对象。但是,string类型只能赋值为字面量,赋值为包装对象就会报错。(只使用小写类型,不使用大写类型)
3.Object 类型与 object 类型
3.1Oject类型
大写的Object类型代表 JavaScript 语言里面的广义对象。所有可以转成对象的值,都是Object类型,这囊括了几乎所有的值。(原始类型值、对象、数组、函数都是合法的Object类型)
let obj:Object;//与let obj:{}一样,只不过后者是简写obj = true;
obj = 'hi';
obj = 1;
obj = { foo: 123 };
obj = [1, 2];
obj = (a:number) => a + 1;
除了undefined
和null
这两个值不能转为对象,其他任何值都可以赋值给Object
类型
let obj:Object;obj = undefined; // 报错
obj = null; // 报错
3.2object类型
小写的object
类型代表 JavaScript 里面的狭义对象,即可以用字面量表示的对象(包含对象、数组和函数,不包括原始类型的值)
let obj:object;obj = { foo: 123 };
obj = [1, 2];
obj = (a:number) => a + 1;
obj = true; // 报错
obj = 'hi'; // 报错
obj = 1; // 报错
大多数时候,我们使用对象类型,只希望包含真正的对象,不希望包含原始类型。所以,建议总是使用小写类型object,不使用大写类型Object
注意,无论是大写的Object类型,还是小写的object类型,都只包含 JavaScript 内置对象原生的属性和方法,用户自定义的属性和方法都不存在于这两个类型之中
4.undefined和null的特殊性
undefined和null既是值,又是类型
作为值,它们有一个特殊的地方:任何其他类型的变量都可以赋值为undefined或null。
JavaScript 的行为是,变量如果等于undefined就表示还没有赋值,如果等于null就表示值为空。所以,TypeScript 就允许了任何类型的变量都可以赋值为这两个值。
let age:number = 24;age = null; // 报错
age = undefined; // 报错
上面示例中,打开--strictNullChecks
以后,number
类型的变量age
就不能赋值为undefined
和null
。
这个选项在配置文件tsconfig.json
的写法如下。
{"compilerOptions": {"strictNullChecks": true// ...}
}
打开strictNullChecks
以后,undefined
和null
这两种值也不能互相赋值了。undefined
和null
只能赋值给自身,或者any
类型和unknown
类型的变量
let x:any = undefined;
let y:unknown = null;
5.联合类型
联合类型(union types)指的是多个类型组成的一个新类型,使用符号|
表示。
联合类型A|B
表示,任何一个类型只要属于A
或B
,就属于联合类型A|B
。
let x:string|number;x = 123; // 正确
x = 'abc'; // 正确
6.交叉类型
交叉类型(intersection types)指的多个类型组成的一个新类型,使用符号&
表示
交叉类型的主要用途是表示对象的合成:
let obj:{ foo: string } &{ bar: string };obj = {foo: 'hello',bar: 'world'
};
上面示例中,变量obj
同时具有属性foo
和属性bar
。
交叉类型常常用来为对象类型添加新属性。
type A = { foo: number };type B = A & { bar: number };
上面示例中,类型B
是一个交叉类型,用来在A
的基础上增加了属性bar
。
7.type命令
type
命令用来定义一个类型的别名。
type Age = number;let age:Age = 55;
上面示例中,type
命令为number
类型定义了一个别名Age
。这样就能像使用number
一样,使用Age
作为类型。
8.typeof运算符
TypeScript 将typeof
运算符移植到了类型运算,它的操作数依然是一个值,但是返回的不是字符串,而是该值的 TypeScript 类型。
const a = { x: 0 };type T0 = typeof a; // { x: number }
type T1 = typeof a.x; // number
上面示例中,typeof a表示返回变量a的 TypeScript 类型({ x: number })。同理,typeof a.x返回的是属性x的类型(number)。
这种用法的typeof返回的是 TypeScript 类型,所以只能用在类型运算之中(即跟类型相关的代码之中),不能用在值运算
type T = typeof Date(); // 报错
上面示例会报错,原因是 typeof 的参数不能是一个值的运算式,而Date()
需要运算才知道结果。
另外,typeof
命令的参数不能是类型。
type Age = number;
type MyAge = typeof Age; // 报错
上面示例中,Age是一个类型别名,用作typeof命令的参数就会报错。
typeof 是一个很重要的 TypeScript 运算符,有些场合不知道某个变量foo的类型,这时使用typeof foo就可以获得它的类型。
9.块级类型声明
TypeScript 支持块级类型声明,即类型可以声明在代码块(用大括号表示)里面,并且只在当前代码块有效
if (true) {type T = number;let v:T = 5;
} else {type T = string;let v:T = 'hello';
}
10.类型的兼容
type T = number|string;let a:number = 1;
let b:T = a;
变量a
和b
的类型是不一样的,但是变量a
赋值给变量b
并不会报错。这时,我们就认为,b
的类型兼容a
的类型。
如果类型A
的值可以赋值给类型B
,那么类型A
就称为类型B
的子类型(subtype)。在上例中,类型number
就是类型number|string
的子类型
let a:'hi' = 'hi';
let b:string = 'hello';b = a; // 正确
a = b; // 报错
上面示例中,hi
是string
的子类型,string
是hi
的父类型。所以,变量a
可以赋值给变量b
,但是反过来就会报错。
子类型继承了父类型的所有特征,所以可以用在父类型的场合。但是,子类型还可能有一些父类型没有的特征,所以父类型不能用在子类型的场合。
3.数组
TypeScript 数组有一个根本特征:所有成员的类型必须相同,但是成员数量是不确定的,可以是无限数量的成员,也可以是零成员
声明的方式:
let arr:number[] = [1, 2, 3];
let arr:(number|string)[];
let arr:Array<number> = [1, 2, 3];
数组类型声明了以后,成员数量是不限制的,任意数量的成员都可以,也可以是空数组。
let arr:number[];
arr = [];
arr = [1];
arr = [1, 2];
arr = [1, 2, 3];let arr1:number[] = [1, 2, 3];arr1[3] = 4;
arr1.length = 2;arr1// [1, 2]
上面示例中,数组arr无论有多少个成员,都是正确的。
这种规定的隐藏含义就是,数组的成员是可以动态变化的,数组增加成员或减少成员,都是可以的。
1.类型推断
如果数组变量没有声明类型,TypeScript 就会推断数组成员的类型。这时,推断行为会因为值的不同,而有所不同。后面,为这个数组赋值时,TypeScript 会自动更新类型推断。
const arr = [];
arr // 推断为 any[]arr.push(123);
arr // 推断类型为 number[]arr.push('abc');
arr // 推断类型为 (string|number)[]
但是,类型推断的自动更新只发生初始值为空数组的情况。如果初始值不是空数组,类型推断就不会更新。
// 推断类型为 number[]
const arr = [123];arr.push('abc'); // 报错
上面示例中,数组变量arr
的初始值是[123]
,TypeScript 就推断成员类型为number
。新成员如果不是这个类型,TypeScript 就会报错,而不会更新类型推断。
2.只读数组,const断言
TypeScript 允许声明只读数组,方法是在数组类型前面加上readonly
关键字。arr
是一个只读数组,删除、修改、新增数组成员都会报错。
const arr:readonly number[] = [0, 1];arr[1] = 2; // 报错
arr.push(3); // 报错
delete arr[0]; // 报错
TypeScript 将readonly number[]
与number[]
视为两种不一样的类型,后者是前者的子类型。
所以子类型number[]
可以用于所有使用父类型的场合,反过来就不行。
let a1:number[] = [0, 1];
let a2:readonly number[] = a1; // 正确a1 = a2; // 报错
只读数组还有一种声明方法,就是使用“const 断言”。
const arr = [0, 1] as const;arr[0] = [2]; // 报错
上面示例中,as const
告诉 TypeScript,推断类型时要把变量arr
推断为只读数组,从而使得数组成员无法改变。
3.多维数组
var multi:number[][] =[[1,2,3], [23,24,25]];
4.元组
它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同
数组的成员类型写在方括号外面(number[]
),元组的成员类型是写在方括号里面([number]
// 数组
let a:number[] = [1];// 元组
let t:[number] = [1];
使用元组时,必须明确给出类型声明(上例的[number]
),不能省略,否则 TypeScript 会把一个值自动推断为数组。
// a 的类型被推断为 (number | boolean)[]
let a = [1, true];
元组成员的类型可以添加问号后缀(?
),表示该成员是可选的。所有可选成员必须在必选成员之后。
let a:[number, number?] = [1];
由于需要声明每个成员的类型,所以大多数情况下,元组的成员数量是有限的,从类型声明就可以明确知道,元组包含多少个成员,越界的成员会报错。
let x:[string, string] = ['a', 'b'];x[2] = 'c'; // 报错
但是,使用扩展运算符(...
),可以表示不限成员数量的元组。
type NamedNums = [string,...number[]
];const a:NamedNums = ['A', 1, 2];
const b:NamedNums = ['B', 1, 2, 3];
1.只读元组
元组也可以是只读的,不允许修改,有两种写法。
// 写法一
type t = readonly [number, string]// 写法二
type t = Readonly<[number, string]>
跟数组一样,只读元组是元组的父类型。所以,元组可以替代只读元组,而只读元组不能替代元组。
type t1 = readonly [number, number];
type t2 = [number, number];let x:t2 = [1, 2];
let y:t1 = x; // 正确x = y; // 报错
2、成员数量推断
function f(point: [number, number]) {if (point.length === 3) { // 报错// ...}
}
上面示例会报错,原因是 TypeScript 发现元组point
的长度是2
,不可能等于3
,这个判断无意义
如果包含了可选成员,TypeScript 会推断出可能的成员数量。
function f(point:[number, number?, number?]
) {if (point.length === 4) { // 报错// ...}
}
如果使用了扩展运算符,TypeScript 就无法推断出成员数量。
const myTuple:[...string[]]= ['a', 'b', 'c'];if (myTuple.length === 4) { // 正确// ...
}
一旦扩展运算符使得元组的成员数量无法推断,TypeScript 内部就会把该元组当成数组处理
3.扩展运算符与成员数量
如果函数调用时,使用扩展运算符传入函数参数,可能发生参数数量与数组长度不匹配的报错。
const arr = [1, 2];function add(x:number, y:number){// ...
}add(...arr) // 报错
解决这个问题的一个方法,就是把成员数量不确定的数组,写成成员数量确定的元组,再使用扩展运算符。
const arr:[number, number] = [1, 2];function add(x:number, y:number){// ...
}add(...arr) // 正确
5.symbol类型
Symbol 值通过Symbol()
函数生成。在 TypeScript 里面,Symbol 的类型使用symbol
表示。
let x:symbol = Symbol();
let y:symbol = Symbol();x === y // false
上面示例中,变量x
和y
的类型都是symbol
,且都用Symbol()
生成,但是它们是不相等的。
1.unique symbol
symbol
的一个子类型unique symbol
,它表示单个的、某个具体的 Symbol 值。
因为unique symbol
表示单个值,所以这个类型的变量是不能修改值的,只能用const
命令声明,不能用let
声明。
// 正确
const x:unique symbol = Symbol();// 报错
let y:unique symbol = Symbol();const x:unique symbol = Symbol();
// 等同于
const x = Symbol();
每个声明为unique symbol
类型的变量,它们的值都是不一样的,其实属于两个值类型。
const a:unique symbol = Symbol();
const b:unique symbol = Symbol();a === b // 报错
变量a
和b
是两个类型,就不能把一个赋值给另一个。
const a:unique symbol = Symbol();
const b:unique symbol = a; // 报错
如果要写成与变量a
同一个unique symbol
值类型,只能写成类型为typeof a
。
const a:unique symbol = Symbol();
const b:typeof a = a; // 正确
unique symbol 类型是 symbol 类型的子类型,所以可以将前者赋值给后者,但是反过来就不行。
const a:unique symbol = Symbol();const b:symbol = a; // 正确const c:unique symbol = b; // 报错
2.类型推断
let
命令声明的变量,推断类型为 symbol。
// 类型为 symbol
let x = Symbol();
const
命令声明的变量,推断类型为 unique symbol。
// 类型为 unique symbol
const x = Symbol();
但是,const
命令声明的变量,如果赋值为另一个 symbol 类型的变量,则推断类型为 symbol。
let x = Symbol();// 类型为 symbol
const y = x;
let
命令声明的变量,如果赋值为另一个 unique symbol 类型的变量,则推断类型还是 symbol。
const x = Symbol();// 类型为 symbol
let y = x;
6.函数类型
数的类型声明,需要在声明函数时,给出参数的类型和返回值的类型。
function hello(txt:string
):void {console.log('hello ' + txt);
}
上面示例中,函数hello()在声明时,需要给出参数txt的类型(string),以及返回值的类型(void),后者写在参数列表的圆括号后面。void类型表示没有返回值(返回值的类型通常可以不写,因为 TypeScript 自己会推断出来。)
如果变量被赋值为一个函数,变量的类型有两种写法。
// 写法一
const hello = function (txt:string) {console.log('hello ' + txt);
}// 写法二
const hello:(txt:string) => void
= function (txt) {console.log('hello ' + txt);
};
如果函数的类型定义很冗长,或者多个函数使用同一种类型,写法二用起来就很麻烦。因此,往往用type
命令为函数类型定义一个别名,便于指定给其他变量。
type MyFunc = (txt:string) => void;const hello:MyFunc = function (txt) {console.log('hello ' + txt);
};
函数的实际参数个数,可以少于类型指定的参数个数,但是不能多于,即 TypeScript 允许省略参数。
let myFunc:(a:number, b:number) => number;myFunc = (a:number) => a; // 正确myFunc = (a:number, b:number, c:number
) => a + b + c; // 报错
如果一个变量要套用另一个函数类型,有一个小技巧,就是使用typeof
运算符。
function add(x:number,y:number
) {return x + y;
}const myAdd:typeof add = function (x, y) {return x + y;
}
1.Function类型
TypeScript 提供 Function 类型表示函数,任何函数都属于这个类型。(不建议这样使用,不然传入和传出的都是any类型)
function doSomething(f:Function) {return f(1, 2, 3);
}
2.箭头函数
箭头函数是普通函数的一种简化写法,它的类型写法与普通函数类似。
const repeat = (str:string,times:number
):string => str.repeat(times);
看一个例子。
type Person = { name: string };const people = ['alice', 'bob', 'jan'].map((name):Person => ({name})
);
上面示例中,Person是一个类型别名,代表一个对象,该对象有属性name。变量people是数组的map()方法的返回值。
map()方法的参数是一个箭头函数(name):Person => ({name}),该箭头函数的参数name的类型省略了,因为可以从map()的类型定义推断出来,箭头函数的返回值类型为Person。相应地,变量people的类型是Person[]。
至于箭头后面的({name}),表示返回一个对象,该对象有一个属性name,它的属性值为变量name的值。这里的圆括号是必须的,否则(name):Person => {name}的大括号表示函数体,即函数体内有一行语句name,同时由于没有return语句,这个函数不会返回任何值。
3.可选参数
如果函数的某个参数可以省略,则在参数名后面加问号表示。(参数名带有问号,表示该参数的类型实际上是原始类型|undefined
,它有可能为undefined
)
function f(x?:number) {// ...
}f(); // OK
f(10); // OK
函数体内部用到可选参数时,需要判断该参数是否为undefined
。
let myFunc:(a:number, b?:number) => number; myFunc = function (x, y) {if (y === undefined) {return x;}return x + y;
}
上面示例中,由于函数的第二个参数为可选参数,所以函数体内部需要判断一下,该参数是否为空。
4.参数默认值
设置了默认值的参数,就是可选的。如果不传入该参数,它就会等于默认值。(可选参数与默认值不能同时使用。)
function createPoint(x:number = 0,y:number = 0
):[number, number] {return [x, y];
}createPoint() // [0, 0]
具有默认值的参数如果不位于参数列表的末尾,调用时不能省略,如果要触发默认值,必须显式传入undefined
。
function add(x:number = 0,y:number
) {return x + y;
}add(1) // 报错
add(undefined, 1) // 正确
5.参数解构
函数参数如果存在变量解构,类型写法如下。
function f([x, y]: [number, number]
) {// ...
}function sum({ a, b, c }: {a: number;b: number;c: number}
) {console.log(a + b + c);
}
参数解构可以结合类型别名(type 命令)一起使用,代码会看起来简洁一些。
type ABC = { a:number; b:number; c:number };function sum({ a, b, c }:ABC) {console.log(a + b + c);
}
6.rest参数
rest 参数表示函数剩余的所有参数,它可以是数组(剩余参数类型相同),也可能是元组(剩余参数类型不同)。
// rest 参数为数组
function joinNumbers(...nums:number[]) {// ...
}// rest 参数为元组
function f(...args:[boolean, number]) {// ...
}
下面是一个 rest 参数的例子。
function multiply(n:number, ...m:number[]) {return m.map((x) => n * x);
}
上面示例中,参数m
就是 rest 类型,它的类型是一个数组。
rest 参数甚至可以嵌套。
function f(...args:[boolean, ...string[]]) {// ...
}
rest 参数可以与变量解构结合使用。
function repeat(...[str, times]: [string, number]
):string {return str.repeat(times);
}// 等同于
function repeat(str: string,times: number
):string {return str.repeat(times);
}
7.readonly只读参数
如果函数内部不能修改某个参数,可以在函数定义时,在参数类型前面加上readonly
关键字,表示这是只读参数。
function arraySum(arr:readonly number[]
) {// ...arr[0] = 0; // 报错
}
8.void类型
void 类型表示函数没有返回值。(如果返回其他值,就会报错,但是允许返回null和undefined,如果打开了strictNullChecks编译选项,那么 void 类型只允许返回undefined。如果返回null,就会报错。这是因为 JavaScript 规定,如果函数没有返回值,就等同于返回undefined。)
function f():void {console.log('hello');
}
需要特别注意的是,如果变量、对象方法、函数参数的类型是 void 类型的函数,那么并不代表不能赋值为有返回值的函数。恰恰相反,该变量、对象方法和函数参数可以接受返回任意值的函数,这时并不会报错。
type voidFunc = () => void;const f:voidFunc = () => {return 123;
};
这是因为,这时 TypeScript 认为,这里的 void 类型只是表示该函数的返回值没有利用价值,或者说不应该使用该函数的返回值。只要不用到这里的返回值,就不会报错。
const src = [1, 2, 3];
const ret = [];src.forEach(el => ret.push(el));
上面示例中,push()
有返回值,表示新插入的元素在数组里面的位置。但是,对于forEach()
方法来说,这个返回值是没有作用的,根本用不到,所以 TypeScript 不会报错。
9.never类型
never
类型表示肯定不会出现的值。它用在函数的返回值,就表示某个函数肯定不会返回值,即函数不会正常执行结束。
它主要有以下两种情况。
(1)抛出错误的函数。
function fail(msg:string):never {throw new Error(msg);
}
上面示例中,函数fail()
会抛错,不会正常退出,所以返回值类型是never
。
注意,只有抛出错误,才是 never 类型。如果显式用return
语句返回一个 Error 对象,返回值就不是 never 类型。
function fail():Error {return new Error("Something failed");
}
上面示例中,函数fail()
返回一个 Error 对象,所以返回值类型是 Error。
(2)无限执行的函数。
const sing = function():never {while (true) {console.log('sing');}
};
注意,never
类型不同于void
类型。前者表示函数没有执行结束,不可能有返回值;后者表示函数正常执行结束,但是不返回值,或者说返回undefined
。
如果程序中调用了一个返回值类型为never
的函数,那么就意味着程序会在该函数的调用位置终止,永远不会继续执行后续的代码。
10.局部类型
函数内部允许声明其他类型,该类型只在函数内部有效,称为局部类型。
function hello(txt:string) {type message = string;let newTxt:message = 'hello ' + txt;return newTxt;
}const newTxt:message = hello('world'); // 报错
上面示例中,类型message是在函数hello()内部定义的,只能在函数内部使用。在函数外部使用,就会报错。
11.函数重载
有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为。这种根据参数类型不同,执行不同逻辑的行为,称为函数重载(function overload)。
reverse('abc') // 'cba'
reverse([1, 2, 3]) // [3, 2, 1]
分别对函数reverse()
的两种参数情况,给予了类型声明。但是,到这里还没有结束,后面还必须对函数reverse()
给予完整的类型声明。
function reverse(str:string):string;
function reverse(arr:any[]):any[];
function reverse(stringOrArray:string|any[]
):string|any[] {if (typeof stringOrArray === 'string')return stringOrArray.split('').reverse().join('');elsereturn stringOrArray.slice().reverse();
}
函数体内部需要判断参数的类型及个数,并根据判断结果执行不同的操作。
function add(x:number,y:number
):number;
function add(x:any[],y:any[]
):any[];
function add(x:number|any[],y:number|any[]
):number|any[] {if (typeof x === 'number' && typeof y === 'number') {return x + y;} else if (Array.isArray(x) && Array.isArray(y)) {return [...x, ...y];}throw new Error('wrong parameters');
}
重载声明的排序很重要,因为 TypeScript 是按照顺序进行检查的,一旦发现符合某个类型声明,就不再往下检查了,所以类型最宽的声明应该放在最后面,防止覆盖其他类型声明。
function f(x:any):number;
function f(x:string): 0|1;
function f(x:any):any {// ...
}const a:0|1 = f('hi'); // 报错
上面声明中,第一行类型声明x:any
范围最宽,导致函数f()
的调用都会匹配这行声明,无法匹配第二行类型声明,所以最后一行调用就报错了,因为等号两侧类型不匹配,左侧类型是0|1
,右侧类型是number
。这个函数重载的正确顺序是,第二行类型声明放到第一行的位置
7.对象
1.可选属性
如果某个属性是可选的(即可以忽略),需要在属性名后面加一个问号。(可选属性等同于允许赋值为undefined
)
const obj: {x: number;y?: number;
} = { x: 1 };
/ 写法一
let firstName = (user.firstName === undefined)? 'Foo' : user.firstName;
let lastName = (user.lastName === undefined)? 'Bar' : user.lastName;// 写法二
let firstName = user.firstName ?? 'Foo';
let lastName = user.lastName ?? 'Bar';
上面示例中,写法一使用三元运算符?:,判断是否为undefined,并设置默认值。写法二使用 Null 判断运算符??,与写法一的作用完全相同。
TypeScript 提供编译设置ExactOptionalPropertyTypes,只要同时打开这个设置和strictNullChecks,可选属性就不能设为undefined
// 打开 ExactOptionsPropertyTypes 和 strictNullChecks
const obj: {x: number;y?: number;
} = { x: 1, y: undefined }; // 报错
上面示例中,打开了这两个设置以后,可选属性就不能设为undefined
了。
注意,可选属性与允许设为undefined
的必选属性是不等价的。
type A = { x:number, y?:number };
type B = { x:number, y:number|undefined };const ObjA:A = { x: 1 }; // 正确
const ObjB:B = { x: 1 }; // 报错
上面示例中,属性y
如果是一个可选属性,那就可以省略不写;如果是允许设为undefined
的必选属性,一旦省略就会报错,必须显式写成{ x: 1, y: undefined }
2.只读属性
属性名前面加上readonly
关键字,表示这个属性是只读属性,不能修改。
interface MyInterface {readonly prop: number;
}
注意,如果属性值是一个对象,readonly
修饰符并不禁止修改该对象的属性,只是禁止完全替换掉该对象。
interface Home {readonly resident: {name: string;age: number};
}const h:Home = {resident: {name: 'Vicky',age: 42}
};h.resident.age = 32; // 正确
h.resident = {name: 'Kate',age: 23
} // 报错
如果希望属性值是只读的,除了声明时加上readonly
关键字,还有一种方法,就是在赋值时,在对象后面加上只读断言a
const myUser = {name: "Sabrina",
} as const;myUser.name = "Cynthia"; // 报错
注意,上面的as const
属于 TypeScript 的类型推断,如果变量明确地声明了类型,那么 TypeScript 会以声明的类型为准。
const myUser:{ name: string } = {name: "Sabrina",
} as const;myUser.name = "Cynthia"; // 正确
3.属性名的索引类型
type MyObj = {[property: string]: string
};const obj:MyObj = {foo: 'a',bar: 'b',baz: 'c',
};
上面示例中,类型MyObj的属性名类型就采用了表达式形式,写在方括号里面。[property: string]的property表示属性名,这个是可以随便起的,它的类型是string,即属性名类型为string。也就是说,不管这个对象有多少属性,只要属性名为字符串,且属性值也是字符串,就符合这个类型声明。
4.解构赋值
解构赋值用于直接从对象中提取属性。
const {id, name, price} = product;
上面语句从对象product
提取了三个属性,并声明属性名的同名变量。
5.结构类型原则
只要对象 B 满足 对象 A 的结构特征,TypeScript 就认为对象 B 兼容对象 A 的类型,这称为“结构类型”原则(structural typing)。
type A = {x: number;
};type B = {x: number;y: number;
};
对象A
只有一个属性x
,类型为number
。对象B
满足这个特征,因此兼容对象A
,只要可以使用A
的地方,就可以使用B
。
const B = {x: 1,y: 1
};const A:{ x: number } = B; // 正确
8.interface
1.interface对象的5种语法
- 对象属性
- 对象的属性索引
- 对象方法
- 函数
- 构造函数
(1)对象属性
interface Point {x: number;y: number;
}
上面示例中,x
和y
都是对象的属性,分别使用冒号指定每个属性的类型。
(2)对象的属性索引
interface A {[prop: string]: number;
}
上面示例中,[prop: string]
就是属性的字符串索引,表示属性名只要是字符串,都符合类型要求。
(3)对象的方法
对象的方法共有三种写法。
// 写法一
interface A {f(x: boolean): string;
}// 写法二
interface B {f: (x: boolean) => string;
}// 写法三
interface C {f: { (x: boole
(4)函数
interface 也可以用来声明独立的函数。
interface Add {(x:number, y:number): number;
}const myAdd:Add = (x,y) => x + y;
上面示例中,接口Add
声明了一个函数类型。
(5)构造函数
interface 内部可以使用new
关键字,表示构造函数。
interface ErrorConstructor {new (message?: string): Error;
}
2.interface的继承
2.1.interface继承interface
interface 可以使用extends
关键字,继承其他 interface。
interface Shape {name: string;
}interface Circle extends Shape {radius: number;
}
上面示例中,Circle
继承了Shape
,所以Circle
其实有两个属性name
和radius
。这时,Circle
是子接口,Shape
是父接口。
extends
关键字会从继承的接口里面拷贝属性类型,这样就不必书写重复的属性。
interface 允许多重继承。
interface Style {color: string;
}interface Shape {name: string;
}interface Circle extends Style, Shape {radius: number;
}
上面示例中,Circle
同时继承了Style
和Shape
,所以拥有三个属性color
、name
和radius
。
多重接口继承,实际上相当于多个父接口的合并
2.2interface继承type
interface 可以继承type
命令定义的对象类型。i
type Country = {name: string;capital: string;
}interface CountryWithPop extends Country {population: number;
}
上面示例中,CountryWithPop继承了type命令定义的Country对象,并且新增了一个population属性。
注意,如果type命令定义的类型不是对象,interface 就无法继承。
2.3interface继承class
interface 还可以继承 class,即继承该类的所有成员。
class A {x:string = '';y():boolean {return true;}
}interface B extends A {z: number
}
上面示例中,B
继承了A
,因此B
就具有属性x
、y()
和z
。
实现B
接口的对象就需要实现这些属性。
const b:B = {x: '',y: function(){ return true },z: 123
}
上面示例中,对象b
就实现了接口B
,而接口B
又继承了类A
。
3.接口合并
多个同名接口会合并成一个接口。
interface Box {height: number;width: number;
}interface Box {length: number;
}
上面示例中,两个Box接口会合并成一个接口,同时有height、width和length三个属性。
4.interface与type的异同
interface命令与type命令作用类似,都可以表示对象类型。
很多对象类型既可以用 interface 表示,也可以用 type 表示。而且,两者往往可以换用,几乎所有的 interface 命令都可以改写为 type 命令。
它们的相似之处,首先表现在都能为对象类型起名。
type Country = {name: string;capital: string;
}interface Coutry {name: string;capital: string;
}
上面示例是type命令和interface命令,分别定义同一个类型。
4.1interface与type的区别:
(1)type能够表示非对象类型,而interface只能表示对象类型(包括数组、函数等)。
(2)interface可以继承其他类型,type不支持继承。
继承的主要作用是添加属性,type定义的对象类型如果想要添加属性,只能使用&运算符,重新定义一个类型。
type Animal = {name: string
}type Bear = Animal & {honey: boolean
}
上面示例中,类型Bear
在Animal
的基础上添加了一个属性honey
。上例的&
运算符,表示同时具备两个类型的特征,因此可以起到两个对象类型合并的作用。
作为比较,interface
添加属性,采用的是继承的写法。
interface Animal {name: string
}interface Bear extends Animal {honey: boolean
}
继承时,type 和 interface 是可以换用的。interface 可以继承 type。
type Foo = { x: number; };interface Bar extends Foo {y: number;
}
type 也可以继承 interface。
interface Foo {x: number;
}type Bar = Foo & { y: number; };
(3)同名interface
会自动合并,同名type
则会报错。也就是说,TypeScript 不允许使用type
多次定义同一个类型。
type A = { foo:number }; // 报错
type A = { bar:number }; // 报错
上面示例中,type
两次定义了类型A
,导致两行都会报错。
(4)interface
不能包含属性映射(mapping),type
可以
interface Point {x: number;y: number;
}// 正确
type PointCopy1 = {[Key in keyof Point]: Point[Key];
};// 报错
interface PointCopy2 {[Key in keyof Point]: Point[Key];
};
(5)this
关键字只能用于interface
。
// 正确
interface Foo {add(num:number): this;
};// 报错
type Foo = {add(num:number): this;
};
6)type 可以扩展原始数据类型,interface 不行。
// 正确
type MyStr = string & {type: 'new'
};// 报错
interface MyStr extends string {type: 'new'
}
(7)interface
无法表达某些复杂类型(比如交叉类型和联合类型),但是type
可以。
type A = { /* ... */ };
type B = { /* ... */ };type AorB = A | B;
type AorBwithName = AorB & {name: string
};
上面示例中,类型AorB是一个联合类型,AorBwithName则是为AorB添加一个属性。这两种运算,interface都没法表达。
综上所述,如果有复杂的类型运算,那么没有其他选择只能使用type;一般情况下,interface灵活性比较高,便于扩充类型或自动合并,建议优先使用
相关文章:

ts基础知识
1. any 类型,unknown 类型,never 类型 TypeScript 有两个“顶层类型”(any和unknown),但是“底层类型”只有never唯一一个 1、any 1.1 基本含义 any 类型表示没有任何限制,该类型的变量可以赋予任意类型的…...

KLayout Python Script ------ 绘制1个 Box 物体
KLayout Python Script ------ 绘制两个 Box 物体 引言正文引言 本人通常使用 IPKISS 进行版图绘制,然而很多时候,IPKISS 的功能十分鸡肋。因此,萌生了一种自己写绘制软件的想法,因为 IPKISS 绘制的版图最终也是使用 KLayout 来呈现的,因此,再研究了 KLayout 提供的 API…...

c# 编辑、删除一条数据
1、编辑数据 [HttpPost] public MessageModel<string> Put([FromBody] Dtable request) { var data new MessageModel<string>(); request.UPDATETIME DateTime.Now; if (request.ID>0) { …...

高性能服务系列【八】C10M时代,网络IO库需要重建
在目前网络上能搜索到的,关于网络IO模型的文章,基本都是关于多路复用的iocp/epoll的,这些技术是为了解决C10K问题而提出的解决方案。现代网卡已经普遍支持10Gb,100Gb也不少见,这些解决方案已经无法提升性能的需求。 我…...

Go语言与Rust哪一个更有发展前景?
Go语言和Rust都是目前非常受欢迎的编程语言,它们各自具有独特的优势和适用场景。关于哪一个更有发展前景,这实际上取决于多个因素,包括个人偏好、项目需求、社区支持以及未来技术的发展趋势等。 Go语言是由Google推出的,具有简洁…...

STM32使用定时器驱动电机
STM32使用定时器驱动电机 1、对定时器进行初始化配置1.1、include "encoder.c"文件 主函数 1、对定时器进行初始化配置 1.1、include "encoder.c"文件 #include "encoder.h"void TIM4_Encoder_Init(u16 arr,u16 psc) { GPIO_InitTypeDef GPIO…...

C语言游戏实战(4):人生重开模拟器
前言: 人生重开模拟器是前段时间非常火的一个小游戏,接下来我们将一起学习使用c语言写一个简易版的人生重开模拟器。 网页版游戏: 人生重开模拟器 (ytecn.com) 1.实现一个简化版的人生重开模拟器 (1) 游戏开始的时…...

MVC架构模式学习笔记(动力节点老杜2022)
GitHub代码笔记:laodu-mvc: 动力节点学习javaweb中的mvc笔记。 文章目录 1.视频链接 2.不使用MVC架构模式程序存在的缺陷 3.MVC架构模式理论基础 4.JavaEE设计模式-DAO模式 5.pojo & bean & domain 6.业务层抽取以及业务类实现 7.控制层 8.MVC架构模式与三…...

docker常用操作-docker私有仓库的搭建(Harbor),并将本地镜像推送至远程仓库中。
1、docker-compose安装,下载docker-compose的最新版本 第一步:创建docker-compose空白存放文件vi /usr/local/bin/docker-compose 第二步:使用curl命令在线下载,并制定写入路径 curl -L "https://github.com/docker/compos…...

什么是MVC
MVC的全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,是一种软件设计典范。它是用一种业务逻辑、数据与界面显示分离的方法来组织代码,将众多的业务逻辑聚集到一个部件里面ÿ…...

ChatGPT浪潮来袭!谁先掌握,谁将领先!
任正非在接受采访时说 今后职场上只有两种人, 一种是熟练使用AI的人, 另一种是创造AI工具的人。 虽然这个现实听起来有些夸张的残酷, 但这就是我们必须面对的事实 📆 对于我们普通人来说,我们需要努力成为能够掌握…...
Focal and Global Knowledge Distillation forDetectors
摘要 文章指出,在目标检测中,教师和学生在不同领域的特征差异很大,尤其是在前景和背景中。如果我们 平等地蒸馏它们,特征图之间的不均匀差异将对蒸馏产生负面影响。因此,我们提出了局部和全局蒸馏。局部蒸馏分离前景和…...

FX110网:1月美国零售货币资金环比上升2.61%,嘉盛环比上升1.86%
美国商品期货交易委员会(CFTC)发布的最新月度报告显示,2024年1月零售货币存款与上月相比上升2.61%。 这份报告涵盖在美国运营的注册零售货币对交易商(RFED)和经纪自营商。包括嘉信理财(CHARLES SCHWAB Futu…...

全量知识系统的核心-全量知识的一个“恰当组织”的构想及百度AI答问
全量知识系统的核心-全量知识的一个恰当组织 Q1. 以下是对 我刚刚完成的文档“全量知识系统的核心:全量知识的一个恰当组织构想”的百度AI答复。由于字数400的限制,内容被分成四段. 第一次回答:学科和科学的框架 关于技术学科、一般学科和…...

C++中using 和 typedef 的区别
C中using 和 typedef 的区别_typedef using-CSDN博客 在C中,“using”和“typedef”执行声明类型别名的相同任务。两者之间没有重大区别。C中的“Using”被认为是类型定义同义词。此方法也称为别名声明。定义这些别名声明的工作方式类似于使用“using”语句定义C中…...

LeetCode-1944题: 队列中可以看到的人数(原创)
【题目描述】 有 n 个人排成一个队列,从左到右 编号为 0 到 n - 1 。给你以一个整数数组 heights ,每个整数 互不相同,heights[i] 表示第 i 个人的高度。一个人能 看到 他右边另一个人的条件是这两人之间的所有人都比他们两人 矮 。更正式的&…...

Java基础面试题整理2024/3/13
1、可以使用switch的数据类型 Java5以前,switch(arg)表达式中,arg只能是byte、short、char、int。 Java5之后引入了枚举类型,也可以是枚举类型。 Java7开始引入了字符串类型。 2、Java中的goto有什么作用 goto是Java中的保留字,…...

MachineSink - 优化阅读笔记
注:该优化与全局子表达式消除刚好是相反的过程,具体该不该做这个优化得看代价模型算出来的结果(有采样文件指导算得会更准确) 该优化过程将指令移动到后继基本块中,以便它们不会在不需要其结果的路径上执行。 该优化过程并非旨在替代或完全…...

虾皮shopee根据ID取商品详情 API
公共参数 名称类型必须描述keyString是免费申请调用key(必须以GET方式拼接在URL中)secretString是调用密钥api_nameString是API接口名称(包括在请求地址中)[item_search,item_get,item_search_shop等]cacheString否[yes,no]默认y…...

你知道数据库有哪些约束吗?
目录 1. NULL约束 2. 唯一(UNIQUE)约束 3. 默认值(DEFAULT)约束 4. 主键约束 5. 外键约束 6. CHECK约束 数据库约束是一种用于确保数据库中数据完整性和一致性的规则或条件。这些约束可以应用于表、列或整个数据库࿰…...

QT----基于QT的人脸考勤系统(未完成)
目录 1 编译opencv库1.1 下载源代码1.2 qt编译opencv1.3 执行Cmake一直卡着data: Download: face_landmark_model.dat 2 编译SeetaFace2代码2.1 遇到报错By not providing "FindOpenCV.cmake" in CMAKE_MODULE_PATH this project has2.2遇到报错Model missing 3 测试…...

机试:成绩排名
问题描述: 代码示例: #include <bits/stdc.h> using namespace std;int main(){cout << "样例输入" << endl; int n;int m;cin >> n;int nums[n];for(int i 0; i < n; i){cin >> nums[i];}// 排序for(int i 0; i < n; i){//…...

C编程基础四十分笔记
都是一些基础的C语言 一 输入一个整数,计算这个整数有几位二 编写程序计算一个分布函数三 输入一个字符串,再随便输入一个字母,判断这个字母出现几次四 求 1到10的阶乘之和五 求一个球体体积六 写一个链表,存1,2&#…...

k8s关于pod
目录 1、POD 的创建流程 kubectl 发起创建 Pod 请求: API Server 接收请求并处理: 写入 Etcd 数据库: Kubelet 监听并创建 Pod: Pod 状态更新和汇报: 2、POD 的状态解析 1. Pending Pod 2. Running Pod 3. S…...

yum安装mysql 数据库tab自动补全
centos7上面没有mysql,它的数据库名字叫做mariadb [rootlocalhost ~]#yum install mariadb-server -y [rootlocalhost ~]#systemctl start mariadb.service [rootlocalhost ~]#systemctl stop firewalld [rootlocalhost ~]#setenforce 0 [rootlocalhost ~]#ss -na…...

MBT-Net
feature F,edge feature E-F where r related to the relative position 辅助信息 作者未提供代码...

大数据赋能,能源企业的智慧转型之路
在数字洪流中,大数据已经成为推动产业升级的新引擎。特别是在能源行业,大数据的应用正引领着一场深刻的智慧转型。今天,我们就来探讨大数据如何在能源企业中发挥其独特的魅力,助力企业提效降本,实现绿色发展。 动态监控…...

2024考研国家线公布,各科分数线有哪些变化?考研国家线哪些涨了,哪些跌了?可视化分析告诉你
结论在文章结尾 2024考研国家线 一、近五年国家线趋势图-学术硕士 文学 管理学 工学照顾专业 体育学 交叉学科 军事学 历史学 理学 享受少数名族照顾政策的考生 中医类照顾专业 教育类 艺术类 医学 工学 哲学 法学 农学 经济学 二、近五年国家线趋势图-专业硕士 中医 应用心理 …...

高效、安全的APP分发与推广平台
在信息化快速发展的今天,APP已经成为人们生活中不可或缺的一部分。然而,对于众多APP开发者来说,如何让自己的APP在众多竞争者中脱颖而出,被更多用户所认知和下载,成为了一个亟待解决的问题。这时,一个高效、…...

浅谈异或运算
异或,是一个数学运算符,英文为exclusive OR,缩写为xor,应用于逻辑运算。异或的数学符号为“⊕”,计算机符号为“xor”。其运算法则为: a⊕b (a ∧ b) ∨ (a ∧b…...