原始类型
在typescript中目前为止一共有如下几种原始数据类型
对应JavaScript中的boolean,该类型能够表示两个逻辑值
const yes: boolean = true;const no: boolean = false;
string
对应JavaScript中的string,该类型能够表示采用Unicode UTF-16编码格式存储的字符序列
const str: string = 'str';
number
对应JavaScript中的number,该类型能够表达采用双精度64位二进制浮点数格式存储的数字 ```typescript // 二进制数 const bin: number = 0b1010;
// 八进制数 const oct: number = 0o744;
// 十进制 const int: number = 3; const float: number = 3.14;
// 十六进制 const hex: number = 0xffffff;
<a name="ocLGv"></a>## bigint- 对应JavaScript中的bigint,该类型能够表示任意精度的整数,但也仅能够表示整数,bigint采用了特殊的对象数据结构来表示和存储一个整数```typescript// 二进制整数const bin: bigint = 0b1010n;// 八进制整数const oct: bigint = 0o744n;// 十进制整数const int: bigint = 10n;// 十六进制整数const hex: bigint = 0xffffffn;
symbol与unique symbol
对应JavaScript中的symbol,该类型能够表示任意类型的symbol值
const s0: symbol = Symbol();const s1: symbol = Symbol.for('foo');const s2: symbol = Symbol.hasInstance;const s3: symbol = s0;
为了能够将symbol值视作为固定值的字面量,typescript引入了 unique symbol 类型
const s0: unique symbol = Symbol();const s1: unique symbol = Symbol.for('foo');
typescript只允许const声明或者以readonly属性声明来定义 unique symbol 类型的值 ```typescript // 必须使用const声明 const a: unique symbol = Symbol();
interface Foo { // 必须使用readonly修饰符 readonly b: unique symbol; }
class C { // 必须使用static和readonly修饰符 static readonly c: unique symbol = Symbol(); }
- unique symbol 类型的主要作用是用作与接口,类等类型中的可计算属性```typescriptconst x: unique symbol = Symbol();const y: symbol = Symbol();interface Foo {[x]: string;// 错误,接口类型中的计算属性必须引用类型为字面量类型// 或者为 unique symbol 的表达式[y]: string;}
nullable
typescript中nullable类型指的是值可以为undefined和null的类型
undefined
对应JavaScript中的undefined,该类型使用undefined关键字标识
const foo: undefined = undefined;
null
对应JavaScript中的null,该类型可以用null关键字标识
const foo:null = null;
void
void类型表示某个值不存在,该类型用作函数的返回值类型,若一个函数没有返回值,那么该函数的返回值类型为void
function f1(message: number): void {console.log(message)}
枚举类型
枚举类型由零个或者多个枚举成员构成,每个枚举成员都是一个命名常量,通过enum关键字定义
enum Direction {Up,Dwon,Left,Right}
按照枚举成员可以将枚举类型划分为以下三类
- 数值型枚举
- 字符串枚举
-
数值型枚举
数值型枚举是最常用的枚举类型,是number类型的子类型,如果定义枚举时没有设置枚举成员值,根据typescript的规则,第一个枚举成员的值为0,以后每个枚举成员的值等于前一个枚举成员的值加1 ```typescript enum Direction { Up, // 0 Dwon, // 1 Left = 10, // 10 Right // 11 }
const num: number = Direction.Top
<a name="VPYXK"></a>### 字符串枚举- 字符串枚举必须使用字符串字面量或者另一个字符串枚举成员来初始化,字符串枚举成员没有自增长行为```typescriptenum Direction {Up = 'UP',Dwon = 'DWON',Left = 'LEFT',Right = 'RIGHT',U = Up,D = Dwon,L = Left,R = Right}const str: string = Direction.Up
异构型枚举
在同一个枚举中同时定义数值型成员和字符串枚举成员,将这种枚举类型称为异构型枚举
enum Color {Black = 0,White = 'White'}
在定义异构型枚举时,不允许使用计算的值作为枚举成员的初始值
enum Color {// 含有字符串的枚举中不允许使用计算值Black = 0 + 1,White = 'White'}
在定义的异构型枚举时,必须为跟紧在字符串后面的枚举成员指定一个初始值
enum Color {Black,White = 'White'}enum Color {White = 'White',Black// 枚举成员必须有一个初始值}
枚举成员映射
无论哪种类型的枚举,都可以通过枚举成员名访问枚举成员值,反过来也可以通过枚举成员值去获取枚举成员名 ```typescript enum Bool { False = 0, True = 1 }
Bool.False // 0 Bool.True // 1
Bool[Bool.False] // ‘False’ Bool[Bool.True] // ‘True’
<a name="IBvIP"></a>### 常量枚举成员与计算枚举成员- 每个枚举成员都有一个值,根据枚举成员值的定义可以将枚举成员划分为以下两类<a name="QFTj0"></a>#### 常量枚举- 若枚举成员的初始值是常量枚举表达式,那么该枚举成员是常量枚举成员,常量枚举表达式是typescript表达式的子集,它能够在编译阶段求值,常量表达式的具体规则如下- 可以是数字字面量,字符串字面量和不包含替换值的模板字面量- 可以对前面定义的常量枚举成员的引用- 可以是用分组运算符包围起来的常量枚举表达式- 可以使用一元运算符,操作数必须为常量枚举表达式- 可以使用二元运算符,两个操作数必须为常量枚举表达式```typescriptenum Foo {A = 0, // 数字字面量B = 'B', // 字符串字面量C = `C`, // 无替换值的模板字面量D = A // 引用前面定义的常量枚举成员}enum Bar {A = -1, // 一元运算符B = 1 + 2, // 二元运算符C = (1 + 2) * 3 // 分组运算符}
计算枚举成员
除常量成员之外的其他枚举成员都属于计算枚举成员
enum Foo {A = 'A'.length,B = Math.pow(2, 3)}
联合枚举类型
当枚举类型中的所有都是字面量枚举成员时,该枚举类型成了联合枚举类型
联合枚举成员类型
联合枚举类型成员除了能够表示一个常量值外,还能够表示一种类型,即联合枚举成员类型 ```typescript enum Foo { A, B, C, D }
const num: Foo.A = Foo.A
- 也可以将联合枚举成员类型赋值给联合枚举类型```typescriptenum Foo {A,B,C,D}const num: Foo.A = Foo.Aconst foo: Foo = num
联合枚举类型
- 联合枚举类型是由所有联合枚举成员构成的联合类型
- 由于联合枚举类型是由固定数量的联合枚举成员类型构成的联合类型,因此编译器能够利用该性质对代码进行类型检查 ```typescript enum Bool { False = 0, True = 1 }
function f1(msg: Bool) { if (msg !== Bool.False || msg !== Bool.True) { // 编译错误,此条件将始终返回 ‘true’,因为类型 ‘Bool.False’ 和 ‘Bool.True’ 没有重叠 } }
```typescriptenum Foo {A = 'A',B = 'B'}enum Bar {A = 'A'}enum Baz {B = 'B',C = 'C'}function f1(x: 'A' | 'B') {console.log(x)}function f2(foo: Foo, bar: Bar, baz: Baz) {f1(foo)f1(bar)// 编译错误,类型'Baz'的参数不能赋给类型'A | 'B'的参数f1(baz)}
字面量类型
typescript支持将字面量作为类型使用,我们称之为字面量类型,每一个字面量都只有一个可能的值,即字面量本身
boolean字面量
boolean字面量类型只有两种,true字面量以及false字面量,boolean字面量类型是boolean类型的子类型,因此也可以将boolean字面量赋值给boolean类型
const a: false = false;const b: true = true;let c: boolean;c = a;c = b;
string字面量
字符串字面量和模板字面量都能够创建字符串,字符串面量和不带参数的模板字面量可以作为string字面量类型使用,string字面量类型是string类型的子类型,因此也可以将string字面量赋值给string类型
const a: 'hello' = 'hello';const b: 'wrod' = 'wrod';let c: string;c = a;c = b;
数字字面量
数字字面量包含number字面量类型和bigint字面量类型,number字面量类型和bigint字面量类型分别是number类型和bigint的子类型,因此也可以将进行赋值操作 ```typescript const one: 1 = 1; const num: number = one;
const oneN: 1n = 1n; const numN: bigint = oneN;
<a name="kJvLA"></a>### 枚举成员字面量类型- 前面介绍了联合枚举成员类型,我们也可以将其称作枚举成员字面量类型,因为联合枚举成员类型使用了枚举成员字面量表示```typescriptenum Foo {A,B}const a: Foo.A = Foo.Aconst b: Foo.B = Foo.B
单元类型
- 单元类型也称作单例类型,指的是仅包含一个可能值的类型
- typescript的单元类型有以下几种
- undefined类型
- null类型
- unique symbol类型
- void类型
- 字面量类型
- 联合枚举成员类型 ```typescript const a: undefined = undefined; const b: null = null; const c: unique symbol = Symbol(); const d: void = undefined; const e: ‘hello’ = ‘hello’;
enum Foo { A }
const f: Foo.A = Foo.A;
<a name="knLCu"></a># 顶端类型- 顶端类型源于数学中的类型论,同时它也被广泛应用于计算机编程语言中,顶端类型是一种通用类型,有时也称为通用超类型,因为在类型系统中,所有类型都是顶端类型的子类型,或者是顶端类型是其他类型的父类型,顶端类型涵盖了类型系统中的所有值- typescript中有两种顶端类型- any- unknown<a name="E40FQ"></a>## any- typescript中,所有类型都是any类型的子类型,我们可以将任何类型的值赋值给any类型```typescriptlet x:any;x = true;x = 'hi';x = 3;x = 1n;x = Symbol();x = undefined;x = null;x = {};x = [];
- 也可以将any类型赋值给其他类型 ```typescript let x:any;
let a: boolean = x; let b: string = x; let c: bigint = x; let d: number = x; let e: symbol = x; let f: void = x; let g: undefined = x; let h: null = x;
- any类型允许执行任意的操作而不会产生编译错误,例如```typescriptconst a: any = 1;a.lengtha()a[1]
- 在程序中我们使用any类型来跳过编译器的类型检查,如果声明某个值的类型为any,那么就相当于告诉编译器,不要对这个值进行类型检查,长远来看,我们因为尽量减少在代码中使用any类型
若一个值没有明确的类型注解,编译器又无法自动推断它的类型,那么这个值默认类型为any类型
function f1(x) {// 参数 "x" 隐式具有 "any" 类型console.log(x)}
unknown
typescript3.0引入了unknown类型,unknown使用unknown类型表示
let a: unknown;
根据顶端类型的性质,其他任何类型都能够赋值给unknown类型,该行为与any类型是一致的 ```typescript let x:unknown;
x = true; x = ‘hi’; x = 3; x = 1n; x = Symbol(); x = undefined; x = null; x = {}; x = [];
- unknown类型是比any更安全的顶端类型,因为unknown类型只允许赋值给any和unknown类型,而不允许赋值给其他任何类型,该行为与any类型是不同的```typescriptlet x:unknown;let a1: any = x;let b1: unknown = x;// 以下编译错误let a: boolean = x;let b: string = x;let c: bigint = x;let d: number = x;let e: symbol = x;let f: void = x;let g: undefined = x;let h: null = x;
- 同时在unknown类型上也不允许绝大部分操作 ```typescript let x: unknown;
// 以下编译错误 x.length;
x();
x[];
- 在程序中使用unknown类型时,必须细化为某种类型,否则将产生编译错误```typescriptfunction f1(x: unknown) {// 编译错误,类型'unknown'上不存在属性'length'console.log(x.length)}function f1(x: unknown) {if (typeof x === 'string') {console.log(x.length)}}
对比
- typescript中仅有any和unknown两种顶端类型
- typescript中所有类型都可以给赋值给any和unknown类型,两者没有写入限制
- any类型能够赋值给其他任何类型,但不包括never类型
- unknown类型仅能赋值给any类型和unknown类型
- 在使用unknown类型之前,我们必须细化为某种类型,而使用any类型时没有任何限制
unknown类型相当于更安全的any类型,在程序中我们尽量减少顶端类型,如果无法知晓某个值的类型,那么建议优先使用unknown类型来代替any类型,因为更安全
尾端类型
typescript2.0引入了仅有的尾端类型never类型,never类型使用never关键字来标识,不包含任何值
function f1(x): never {throw new Error()}
根据尾端类型的定义,never类型是所有类型的子类型,所以never类型允许赋值给任何类型,尽管不存在never类型的值 ```typescript let x: never;
let a: boolean = x; let b: string = x; let c: number = x; let d: bigint = x; let e: symbol = x; let f: void = x; let g: undefined = x; let h: null = x;
- 正如尾端类型其名,它在系统类型中位于类型结构的最底层,没有类型是never类型的子类型,因此never类型自身以外,所有其他类型都不能赋值给never类型```typescriptlet x: never;let y: never;// 正确x = y;// 错误x = true;x = 'hi';x = 1;x = 1n;x = Symbol();x = undefined;x = null;x = {};x = [];x = function() {};let z: any;x = z;
数组类型
数组类型定义
typescript提供了以下两种方式来定义数组类型
简便型数组类型表示法借用了数字字面量的语法,通过在数组元素类型之后添加一对方括号[]来定义数组
TElement []
TElement代表了数组元素类型,方括号[]代表数组类型
const arr: number[] = [1, 2, 3];
如果数组中元素为复合类型,则需要在数组元素类型上使用分组运算符,即小括号
const arr: (number | string)[] = [1, 2, 3, '1', '2', '3'];
泛型数组类型表示法
泛型数组类型是另外一种表示数组类型的方法,顾名思义,泛型数组类型表示法就是使用泛型来表示数组类型
Array<TElement>
TElement代表数组元素类型
const arr: Array<number> = [1, 2, 3]
就算数组元素中的类型为复合类型也不需要使用分组运算符 ```typescript const arr: Array
= [1, 2, 3, ‘1’, ‘2’, ‘3’];
const newArr: Array<{name: string; age: number}> = [{name: ‘zs’, age: 1}];
<a name="1q7zq"></a>## 数组元素类型- 在定义数组类型之后,当访问数组元素能够获得正确的元素类型信息```typescriptconst arr: Array<number> = [1, 2, 3];const num = arr[0];// number 类型
- 需要注意的是typescript无法推断出是否存在数组访问越界的情况,因此即使访问了不存在的数组元素,还是会得到声明的数组元素类型
```typescript
const arr: Array
= [1, 2, 3];
const num = arr[10]; // 没有编译错误
<a name="MPsv1"></a>### 只读数组- 只读数组与常规数组的却别在于,只读数组只允许程序读取数组元素,而不允许修改数组元素- typescript提供了以下三种方式来定义一个只读数组- 使用ReadonlyArray<T>内置工具- 使用readonly修饰符- 使用Readonly<T>工具类型<a name="KHjh2"></a>#### ReadonlyArray<T>- 类型参数T表示数组元素的类型```typescriptconst arr: ReadonlyArray<number> = [1, 2, 3];
readonly
- typescript在3.4引入的新语法,需要注意的是readonly修饰符不允许与泛型数组类型一起使用 ```typescript const arr1: readonly number[] = [1, 2, 3];
// 编译错误
const arr2: readonly Array
<a name="jEqo1"></a>#### Readonly<T>- Readonly<T>是typescript提供的一个内置工具类型,用于定义只读对象类型,该工具能够将类型参数T的所有属性转换为只读属性,由于typescript支持了使用readonly修饰符来定义只读数组,所以从typescript3.4开始使用```typescriptconst arr: Readonly<number[]> = [1, 2, 3];
元组类型
元组(Tuple)表示有限元素构成的有序列表,在typescript中,元组类型是数组类型的子类型,元组是长度固定的数组,并且元组中每个元素都有确定的类型
元组的定义
定义元组类型的语法与定义数组字面量的语法相似
[T0, T1, ..., Tn]
该语法中的T0,T1,Tn表示元组中的元素类型,针对元组中每一个位置上的元素需要定义的数据类型 ```typescript const arr1: [number, number] = [1, 2];
// 编译错误, const arr2: [number, number] = [1, ‘2’]; const arr3: [number, number] = [‘1’, ‘2’];
<a name="Y3qoH"></a>## 只读元组- 元组可以定义为只读元组,这与只读数组是类似的,定义只读元组的方式有两种- 使用readonly修饰符- 使用Readonly<T>工具类型- readonly修饰符```typescriptconst arr: readonly [number, number] = [1, 2];
Readonly修饰符
const arr: Readonly<[number, string]> = [1, '2'];
注意!再给只读元组类型赋值时,允许将常规元组类型赋值给只读元组类型,不允许将只读元组类型赋值给常规元组类型,换句话说,不能通过赋值操作来放宽对只读元组的约束 ```typescript const arr1: [number, number] = [1, 2];
const arr2: readonly [number, number] = [1, 2];
const arr3: readonly [number, number] = arr1;
// 编译错误,类型 ‘readonly [number, number]’ 为 ‘readonly’,不能分配给可变类型 ‘[number, number]’ const arr4: [number, number] = arr2;
<a name="0d145fbd"></a>## 访问元组中的元素- 由于元组本质上是数组,所以我们可以使用访问数组元素的方法去访问元组中的元素,访问元组中指定位置上的元素时,编译器能够推断出相应的元素类型```typescript// numberconst num1 = arr[0];// booleanconst bool1 = arr[1];// 编译错误,不能将类型'number'分配给类型'string'const num2: string = arr[0];// 编译错误,不能将类型'boolean'分配给类型'number'const bool2: number = arr[1];
- 当访问数组中不存在的元素时,不会产生编译错误,与之不同的时,当访问元组中不存在的元素时会产生编译错误 ```typescript const arr: [number, number] = [1, 2];
// 编译错误 const foo = arr[3];
<a name="4kjtE"></a>## 元组类型中的可选元素- 在定义元组类型时,可以将某些元素定义为可选元素,定义元组可选元素的语法是在元素类型之后添加一个?```typescriptlet arr: [number, number] = [1, 2];// 编译错误,不能将类型'[number]'分配给类型'[number, number]'// 源具有 1 个元素,但目标需要 2 个arr = [1];let arr: [number, number?] = [1, 2];// 正确arr = [1];
元组类型中的剩余元素
在定义元组类型时,可以将最后一个元素定义为剩余元素,定义元组剩余元素类型的语法如下
const arr: [number, ...number[]] = [1, 2, 3, 4];
如果元组类型定义中有剩余元素,那么该元组的元素数量时开放的,可以包含零个或者多个指定类型的剩余元素
元组的长度
对于经典类型的元组,即不好含可选元素和剩余元素的元组而言,元组中的元素数量是固定的,也就是说元组拥有一个固定的长度,typescript编译器能够识别出元组的长度,并充分利用该信息来进行类型检查
function f1(arr: [number, number]) {if (arr.length === 3) {// 编译错误,此条件将始终返回 'false',因为类型 '2' 和 '3' 没有重叠}}
当元组类型中包含了可选元素时,元组的长度不再是一个固定的值,编译器可以根据的元组的可选元素数量识别出元组所有可能的长度,进而构造出一个由数字字面量类型构成的联合类型来表示元组的长度
function f1(arr: [number, number?, number?]) {let len = arr.length;len = 1;len = 2;len = 3;// 编译错误,不能将类型'4'分配给类型'1 | 2 | 3'len = 4;}
元组类型与数组类型的兼容性
元组类型允许赋值给常规数组类型和只读数组类型,但只读元组类型只允许赋值给只读数组类型 ```typescript const arr1: [number, number] = [1, 2];
const arr2: Array
const arr3: ReadonlyArray
const arr4: Readonly<[number, number]> = [1, 2]
const arr5: readonly number[] = arr4;
// 类型 ‘readonly [number, number]’ 为 ‘readonly’,不能分配给可变类型 ‘number[]’ const arr6: number[] = arr4;
<a name="oxFii"></a># 对象类型<a name="432b30a5"></a>## Object类型- 这里的Object类型指的是Object类型,而不是JavaScript中内置的Object()构造函数,Object类型表示一种类型,而Object()构造函数是一个值,因此它也有自己的类型,但是要注意的是Object()构造函数类型不是Object类型- Object类型是特殊对象,Object.prototype的类型,该类型主要作用是描述JavaScript中几乎所有对象都共享的属性和方法- Object的具体定义如下所示```typescriptinterface Object {constructor: Function;toString(): string;toLocaleString(): string;valueOf(): Object;hasOwnProperty(v: PropertyKey): boolean;isPrototypeOf(v: Object): boolean;propertyIsEnumerable(v: PropertyKey): boolean}
通过该类型定义可以了解到,Object类型里定义的方法都是通用的对象方法
类型兼容性
Object类型都有一个特点,那就是除了undefined和null值以外,其他任何值都可以赋值给Object类型 ```typescript let obj: Object;
obj = { x: 0 }; obj = true; obj = ‘hi’; obj = 1;
// 编译错误 obj = undefined; obj = null;
<a name="g59xd"></a>### 常见错误- 在使用Object类型时容易出现的一个错误是,将Object类型应用与自定义变量,参数,属性等的类型```typescriptconst point: Object = {x: 0,y: 1}
虽然该代码不会产生任何编译错误,但它是一个明显的使用错误,Object类型的用途是描述Object.prototype对象的类型,即所有对象共享的属性和方法
object
object类型表示非原始类型
const point: object = {x: 0,y: 1}
object类型的关注点在于类型的分类,它强调一个类型是非原始类型,即对象类型,object类型的关注点不是该对象类型具体包含了哪些属性,例如对象是否包含一个name的属性,因此它不允许读取和修改object类型上的自定义属性 ```typescript const obj: object = { name: ‘foo’ }
// 编译错误,类型’object’上不存在属性’name’ obj.name
- 在object类型上仅允许访问对象的公共属性和方法,也就是Object类型中定义的方法和属性```typescriptconst obj: object = {name: 'foo'}obj.toString()obj.valueOf()
类型兼容性
- object类型能够准确的表示非原始类型,因为原始类型不允许赋值给object类型,只有非原始类型才能赋值给object类型 ```typescript let obj: object;
// 编译错误 obj = 1; obj = true; obj = ‘hi’; obj = 1n; obj = undefined; obj = null; obj = Symbol();
// 正确 obj = {}; obj = { x: 0 }; obj = []; obj = new Date(); obj = function() {};
- object类型仅能够赋值给以下三种类型- 顶端类型any和unknow- Object类型- 空对象类型字面量```typescriptconst obj: object = {};const a: any = obj;const b: unknown = obj;// Object类型描述了所有对象共享的方法和属性// 所以很自然的对表示对象类型的object类型// 能够赋值给Object类型const o: Object = obj;
对象类型字面量
- 对象类型字面量与对象字面量的语法相似,在定义对象类型的字面量时,需要将成员依次列出,在各个类型成员之间不但可以使用;进行分隔,也可以使用,进行分隔 ```typescript { TypeMember; TypeMember }
{ TypeMember, TypeMember }
- 对象类型字面量的类型成员可以分为以下五类- 属性签名- 调用签名- 构造签名- 方法签名- 索引签名<a name="4Zbkl"></a>### 属性签名- 属性签名声明了对象类型中属性成员的名称和类型```typescript{PropertyName: Type;}
该语法中PropertyName表示对象属性名,可以为标识符,字符串,数字和可计算属性名,Type表示该属性的类型
const obj: { x: number, y: string } = { x: 0, y: '1' }
属性签名的属性名可以为可计算属性名,但需要该可计算计算属性名满足以下条件之一
- 可计算属性名的类型为string字面量类型或者number字面量类型 ```typescript const a: ‘a’ = ‘a’; const b: 0 = 0; let obj: {
};
- 可计算属性名的类型为unique symbol类型```typescriptconst s: unique symbol = Symbol();let obj: {[s]: boolean};
- 可计算属性复合Symbol.xxx的形式 ```typescript let obj: {
};
- 在属性签名的语法中,表示Type的部分是可以省略的,允许只列出属性名而不定义任何类型,在这种情况下,该属性类型默认为any类型```typescriptlet obj: {x,y};
可选属性
- 在默认情况下通过属性签名定义的对象都是必选属性,如果在属性签名中属性名之后添加一个?那么将定义一个可选属性,在给对象类型赋值时,可选属性可以被略
const obj: { x: number, y?: number } = { x: 1 };
只读属性
- 在属性签名中添加readonly修饰符能够定义对象只读属性,定义只读属性的语法如下所示 ```typescript const obj: { x: number, readonly y?: number } = { x: 1, y: 2 };
// 编译错误,无法分配到 ‘y’ ,因为它是只读属性 obj.y = 1;
<a name="rUrIQ"></a>## 空对象类型字面量- 如果对象类型字面量没有定义任何类型成员,它就成了一种特殊的类型,即空对象类型字面量,空对象类型字面量不带有任何属性的对象类型,因此不允许访问任何自定义属性```typescriptconst obj: {} = { x: 0 };// 编译错误,类型'{}'上不存在属性'x'obj.x
- 在空对象类型字面量上,允许访问对象公共的属性和方法,也就是Object类型上定义的方法和属性,空对象类型字面量与Objcet类型十分相似,单从行为上看两者是可以互换使用的,例如,除了undefined和null以外,其他任何值都可以赋值给Object和空对象类型字面量,同时Object类型和空对象类型字面量也允许互相赋值 ```typescript let obj1: {} = ‘hello’; let obj2: Object = ‘hello’;
obj1.valueOf(); obj1 = obj2;
- 两者的区别在于语义上,Object类型用于描述对象公共属性和方法,而空对象类型字面量强调的是不包含属性的对象类型,同时也可以作为Object类型的代理来使用<a name="97wok"></a>## 弱类型- 弱类型是typescript2.4引入的一个概念,指的是同时满足以下条件的对象类型- 对象类型中至少包含一个属性- 对象类型中所有属性都是可选属性- 对象类型中不包含字符串索引签名,数值索引签名,调用签名和构造函数签名<a name="rquxo"></a>## 多余属性- 多余属性可以理解为多出来的属性,多余属性会对类型间关系判定产生影响,例如一个类型是否为一个类型的子类型或者父类型,以及一个类型是否能够赋值给另外一个类型- 假设存在源对象类型和母表对象类型两个对象类型,那么当满足以下条件时,我们说源对象类型相当于目标对象类型存在的多余属性,具体条件如下- 源对象类型是一个全新的对象字面量类型- 源对象类型中存在一个或多个目标对象类型中不存在的属性- 全新的对象字面量类型指的是由对象字面量推断出的类型```typescriptconst obj: { x: number, y: number } = { x: 0, y: 0 };// 类型注解 类型推断// 对象类型字面量 全新的对象字面量类型
const obj: { x: number } = { x: 0, y: 0 };// y是多余属性
允许多余属性
使用类型断言
- 类型断言能够对类型进行强制转换,例如我们可以将{ x: 0, y: 0 }强制转换为{ x: number },类型断言能够绕开多余属性检查的真正原因是,处于类型断言表达式中的对象字面量将不再是全新的对象字面量类型,因此编译器也就不对对其进行多余属性检查
const obj: { x: number } = { x: 0, y: 0 } as { x: number };
- 类型断言能够对类型进行强制转换,例如我们可以将{ x: 0, y: 0 }强制转换为{ x: number },类型断言能够绕开多余属性检查的真正原因是,处于类型断言表达式中的对象字面量将不再是全新的对象字面量类型,因此编译器也就不对对其进行多余属性检查
为目标对象类型添加索引签名
在函数形式参数列表中,为参数添加类型注解就能够定义参数的类型
function f1(x: number, y: number) {return x + y;}
如果在函数形式参数列表中没有明确指定参数类型,并且编译器也无法推断参数类型,那么参数类型将默认为any类型
function f1(x, y) {// 参数 'x' 和 'y' 隐式具有 'any' 类型,但可以从用法中推断出更好的类型return x + y;}
可选参数类型
在JavaScript中函数每一个参数都是可选参数,而在typescript中默认情况下函数每一个参数都是必选参数,在函数形式参数后面添加一个?就可以将参数声明为可选参数
function f1(x: number, y?: number) {return x + (y ?? 0);}
函数的可选参数必须在函数参数列表的末端位置
function f1(x?: number, y: number) {// 必选参数不能位于可选参数后return (x ?? 0) + y;}
默认参数类型
函数的默认参数类型可以通过类型注解定义,也可以根据默认参数值自动推断出类型
function f1(x: number, y = 0) {return x + y;}
如果定义了默认参数,并且默认参数处于函数参数列表的末端位置,那么该参数将被视为可选参数,并在调用该函数时可以不传入对应的实际参数值 ```typescript function f1(x: number, y = 0) { return x + y; }
f1(1)
- 在语法上同一个函数参数不允许同时声明为可选参数和默认参数,如果默认参数之后存在必选参数,那么该默认参数不是可选的参数```typescriptfunction f1(x?: number = 0) {// 参数不能包含问号和初始化表达式}
function f1(x: number = 0, y: number) {return x + y;}// 应有 2 个参数,但获得 1 个f1(1)
剩余参数类型
数组类型的剩余参数
- 最常见的做法就是将剩余参数类型声明为数组类型 ```typescript function f1(…args: number[]) {
} f1() f1(1) f1(1, 2)
<a name="BVq7J"></a>### 元组类型的剩余参数- 如果剩余参数为元组类型,那么编译器会将剩余参数展开为独立的形式参数类型<a name="q1bHe"></a>#### 常规元组类型```typescriptfunction f1(...args: [boolean, number]) {}// 等同于function f1(args_0: boolean, args_1: number) {}
带有可选元素的元组类型
function f1(...args: [boolean, number?]) {}// 等同于function f1(args_0: boolean, args_1?: number) {}
带有剩余元素的元组类型
function f1(args: [boolean, ...number[]]) {}// 等同于function f1(args_0: boolean, ...args_1: number[]) {}
解构参数类型
- 解构还可以应用到函数参数列表中 ```typescript function f1({ x, y }) {
}
f1({ x: 1, y: 1 })
- 也可以使用类型注解为解构参数添加类型信息```typescriptfunction f1({ x, y } : { x: number, y: number }) {}f2({ x: 1, y: 1 })
返回值类型
在函数形式参数列表之后,可以使用类型注解为函数添加返回值类型
function f1(x: number, y: number): number {return x + y;}
绝大数情况下,typescript能够函数体内的return语句等自动推断出返回值类型,因此我们也可以省略返回值类型
- 在typescript中由一个特殊的空类型void,该类型唯一有意义的使用场景就是作为函数的返回值类型,如果一个函数的返回值类型为void,该函数类型只能返回undefined ```typescript function f1(x: number): void { // 不能将类型’number’分配给类型’void’ return x; }
function f2(): void {
}
function f3(): void { return undefined; }
<a name="ZVbMD"></a>## 函数类型字面量- 函数类型字面量是定义函数类型的方法之一,它能够指定函数的参数类型,返回值类型,具体语法如下```typescriptlet f1: (x: number, y: number) => number;f1 = function(x, y) {return x + y;}
调用签名
- 函数本质上是一个对象,但特殊的地方在于函数是可以调用的对象,因此,可以使用对象类型来表示函数类型,若在对象类型中定义了调用签名类型成员,那么我们称该对象类型为函数类型,下例中,我们使用了对象类型字面量和调用签名定义了一个函数类型 ```typescript let f1: { (x: number, y: number): number };
f1 = function(x, y) { return x + y; }
- 实际上,函数类型字面量是仅包含单个调用签名的对象类型字面量的简写形式```typescriptconst f1: { (x: number, y: number): number } = (x, y) => x + y;const f2: (x: number, y: number) => number = (x, y) => x + y;// trueconsole.log(f1(1, 2) === f2(1, 2))
构造函数类型字面量
const Err: new (msg: string) => Error = Error;const test = new Err('Error');
构造签名
const Err: { new (msg: string): Error } = Error;const test = new Err('Error');
调用签名与构造签名
- 有一些函数可以作为普通函数使用,又可以作为构造函数使用,若在对象类型中同时定义调用签名和构造签名,则表示可以直接调用,也可以作为构造函数使用的函数类型 ```typescript declare const F: { new (x: number): Number, (x: number): number };
const a: number = F(1);
const b: Number = new F(1);
console.log(a, b)
<a name="sjKbH"></a>## 重载函数- 重载函数是指一个函数同时拥有多个同类函数签名```typescriptfunction f1(x: number, y: number): number;function f1(x: Array<number>, y: Array<number>): Array<number>;function f1(x: number | Array<number>, y: number | Array<number>): any {if (typeof x === 'number' && typeof y === 'number') {return x + y;}if (Array.isArray(x) && Array.isArray(y)) {return [...x, ...y];}};
函数重载
- 不带有函数体的函数声明语句叫做函数重载
- 在各个函数重载语句之间以及重载语句与函数实现时间不允许出现其他任何语句 ```typescript function f1(x: number, y: number): number;
// 编译错误 const num1: number = 1;
function f1(x: Array
// 编译错误 const num2: number = 2;
function f1(x: number | Array
<a name="rwwH6"></a>### 函数实现- 每一个重载函数只允许一个函数实现- 并且必须位于所有函数重载语句之后```typescriptfunction f1(x: number, y: number): number;// 编译错误,函数实现缺失或未立即出现在声明之后function f1(x: number | Array<number>, y: number | Array<number>): any {};function f1(x: Array<number>, y: Array<number>): Array<number>;
函数重载解析顺序
- 当程序中调用了一个重载函数时,编译器先构建出一个候选函数重载列表,一个函数重载需要同时满足如下条件才可以成为本次函数调用的候选函数重载
- 函数实际参数的数量不少于函数重载中定义的必选参数是的数量
- 函数实际参数的数量不多于函数重载中定义参数的数量
- 每个实际参数的类型能够赋值给函数重载中定义对应的参数类型
```typescript
function f1(x: number, y: number): number;
function f1(x: Array
, y: Array ): Array ; function f1(x: number | Array , y: number | Array ): any { if (typeof x === ‘number’ && typeof y === ‘number’) { return x + y; } if (Array.isArray(x) && Array.isArray(y)) { return […x, …y]; } };
// 编译错误,没有与此调用匹配的重载。 f1(1, [2])
- 函数重载的解析的顺序依赖于函数重载的声明顺序以及函数签名中是否包含字面量类型```typescriptfunction f1(x: any): number;function f1(x: string): 1 | 0;function f1(x: string | number): any {}// 不能将类型'number'分配给类型'0 | 1'const f: 1 | 0 = f1('hello');
重载函数的类型
- 重载函数的类型可以通过多个调用签名的对象类型来表示 ```typescript function f1(x: string): 1 | 0; function f1(x: any): number; function f1(x: string | number): any { return x }
declare const F: { (x: string): 1 | 0, (x: any): number };
const f: 1 | 0 = F(‘1’)
- 定义重载函数的类型时,需要注意两点- 函数实现的函数签名不属于重载函数的调用签名之一- 调用签名的书写顺序是有意义的,它决定了函数重载的解析顺序,一定要确保更精确的调用签名位于更靠前的位置<a name="a5Jt3"></a>### 函数中的this值- 默认情况下编译器会将函数中的this设置为any类型,并允许程序在this值上执行任意操作```typescriptfunction f1() {this.name = '1';this.name = 1;}
函数的this参数
typescript支持在函数形式参数列表中定义一个特殊的this参数来描述this值的类型
function f1(this: { name: string }) {this.name = 'str';// 编译错误,不能将类型'number'分配给类型'string'this.name = 1;}
this参数固定使用this作为参数名,this参数时一个可选参数,若存在,则必须作为函数形式参数列表中第一个参数
如果我们想定义一个纯函数或者不想让函数代码依赖于this的值,这种情况下我们可以将this参数定义为void类型
function f1(this: void, x: number, y: number) {// 类型'void'上不存在属性'name'this.name.name = 'zs'}
当调用定义了this参数的函数时,若this值的实际类型与函数定义中的期望类型不匹配,则会产生编译错误 ```typescript function f1(this: { bar: string }, x: number) {
}
// 类型为’void’的 “this” 上下文不能分配给类型为’{ bar: string; }’的方法的this f1(1)
// 正确 f1.call({ bar: ‘hello’ }, 1)
<a name="q7F2R"></a># 接口- 类似对象类型字面量,接口类型也能够表示任意的对象类型,不同的是,接口类型能够给对象类型命名以及定义类型参数,接口类型无法表示原始类型<a name="HprqL"></a>## 接口声明- 通过interface关键字进行定义,如法如下```typescriptinterface Point {x: number,y: number}
接口的类型成员也分为如下五类
属性签名声明了对象类型中属性成员的名称和类型,语法如下所示
PropertyName: Type;
该语法中PropertyName表示对象属性名,可以为标识符,字符串,数字和可计算属性
调用签名
调用签名定义了该对象类型表示的函数在调用时的类型参数,参数列表,以及返回值类型
interface Point {(x?: number): number}
构造签名
构造签名定义了该对象类型表示的构造函数在使用new 运算符调用时的参数列表以及返回值的类型
interface Err {new (x?: string): Error}
方法签名
方法签名时声明函数的属性成员的简写
interface Document {getElementById(el: string): HTMLElement | null}
之所以说方法签名时声明函数类型属性成员的简写,是因为方法签名可以改写为具有同等效果的属性签名和函数类型字面量 ```typescript interface Document { getElementById(el: string): HTMLElement | null }
interface Document { getElementById: { (el: string): HTMLElement | null } }
interface Document { getElementById: (el: string) => HTMLElement | null }
<a name="v0jgV"></a>## 索引签名- JavaScript支持用索引去访问对象的属性,即通过[]语法去访问对象属性,索引签名只有以下两种- 字符串索引签名- 数值索引签名<a name="1fxg0"></a>### 字符串索引签名- 字符串索引签名如下所示```typescriptinterface Foo {[prop: string]: Type}
- 该语法中,prop表示索引名,它可以为任何合法的标识符,索引名只起到占位的作用,不代表真实的对象属性名,在字符串索引签名中,索引名必须为string,Type代表索引值的类型,可以为任意类型,一个接口只能定义一个字符串索引签名,字符串索引签名会约束该对象类型中所有属性类型 ```typescript interface Foo {
a: 1, b: 1 | 2,
// 类型’boolean’的属性’c’不能赋给字符串索引类型’number’ c: boolean,
// 类型’() => 1’的属性’d’不能赋给字符串索引类型’number’ d(): 1 }
```typescriptinterface Foo {[prop: string]: string | number}const obj: Foo = {name: 'zs',age: 1,// 不能将类型'boolean”分配给类型'string | number'sex: false}
数值索引签名
- 数值索引签名如下所示 ```typescript interface Foo {
}
- 该语法中,prop表示索引名,它可以为任何合法的标识符,索引名只起到占位的作用,不代表真实的对象属性名,在字符串索引签名中,索引名必须为number,Type代表索引值的类型,可以为任意类型,一个接口只能定义一个字符串索引签名,字符串索引签名会约束该对象类型中所有属性类型```typescriptinterface Foo {[prop: number]: string}const arr: Foo = ['1', '2']// 不能将类型'number'分配给类型'string'const arr: Foo = [1, 2, 3, 4]
可选属性与方法
默认情况下,接口中属性签名和方法签名定义的对象属性都是必选的,我们可以在属性名或者方法名后面添加一个?,从而将该属性或者方法定义为可选的
interface Foo {name: string,age?: number}
如果定义了重载签名,必须全部为可选或者必选
interface Foo {a(x: string): void,// 重载签名必须全部为可选签名或必需签名a?(x: boolean): void}
只读属性与方法
使用readyonly能够定义只读属性,只允许在属性签名和索引签名中使用
interface Foo {readonly name: string,readonly age: number}
若接口定义了只读的索引签名,那么接口类型中所有的属性都是只读属性
interface Foo {readonly [prop: number]: number}
接口继承
接口继承的对象类型如下
- 接口
- 对象类型的类型别名
- 类
- 对象类型的交叉类型
- 一个接口可以继承多个接口,用逗号隔开 ```typescript interface Baz { name: string }
interface Bar { age: number }
interface Foo extends Baz, Bar { weight: number }
const arr: Array
<a name="neWRR"></a># 类型别名<a name="lD2fc"></a>## 类型别名声明```typescripttype Animal = Type;
- type为声明类型别名的关键字,Animal为表示类型别名的名称,Type表示类型别名关联的具体类型
- 类型别名引用的类型可以为任意类型,比如原始类型,对象类型,联合类型,交叉类型 ```typescript type StringType = string; type BooleanType = boolean; type Point = { x: string, y: number }; type Foo = string | boolean; type Bar = StringType | BooleanType;
type IntType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
const Int: IntType = 1;
<a name="iYXbc"></a>## 递归的类型别名- 若类型别名引用的类型为接口类型,对象类型字面量,函数类型和构造函数类型字面量,则允许递归```typescripttype T1 = { name: T1 };type T2 = () => T2;type T3 = new () => T3;
若类型别名引用的类型为数组类型或者元组类型,则允许递归
type T1 = Array<T1>;type T2 = T2[];type T3 = [number, T3];
若类型别名引用的是泛型类或者泛型接口,则允许递归 ```typescript interface T0
{ name: T } type T1 = T0 ;
class T2
- 例子```typescripttype Json =| string| number| null| (() => Json | void)| { [prop: string]: Json }| Array<Json>;const arr: Array<Json> = [{name: '1',age: 1,f() {},info: {age: 2},arr: [{name: 1}]}]
类型别名与接口
- 类型别名可以表示非对象类型,而接口只能表示对象类型
- 接口可以继承而类型别名不可以继承,若要实现类似继承的功能则需要使用一些变通的方法,例如当类型别名表示对象类型时,可以借助交叉类型来实现继承效果 ```typescript type Obj1 = { name: string };
type Obj2 = { age: number } & Obj1; ```
- 接口具有声明合并的行为,而类型别名则不会进行声明合并
