Appearance
String
ts
const foo: string = 'foo'
const bar: String = new String('bar')
const baz: String = String('baz') // Errorts
// TS 默认全局下有 name 变量,无法重新声明。
const name: string = 'Tom'
// 在最后加上 export,TS 就知道这是模块下的 name,不会报错了。
export {}Number
typescript
const decLiteral: number = 6 // 10进制
const hexLiteral: number = 0xf00d // 16进制
const binaryLiteral: number = 0b1010 // 2进制
const octalLiteral: number = 0o744 // 8进制
const notANumber: number = NaN;
const infinityNumber: number = Infinity;Boolean
typescript
const isDone: boolean = trueNull
默认情况下 null 和 undefined 是所有类型的子类型,当 strict 或 strictNullChecks 为 true 时,null 只能赋值给它自身,undefined 只能赋值给它自身和 void。
typescript
let num: number = 10
num = null // Ok
// strict: true 或 strictNullChecks: true
let foo: null = null
foo = undefined // Error
let bar: void = null // Error
let baz: void = undefined // OkUndefined
typescript
const u: undefined = undefined;Symbol
typescript
const s: symbol = Symbol('')Bigint
ts
const n: bigint = BigInt(1)Any
typescript
// 如果变量在声明时没有指定类型,会被标识为 any 类型。
let foo
foo = 1
foo = 'hello world'Object 类型的变量只是允许你给它赋任意值,但是却不能够在它上面调用任意的方法,即便它真的 有这些方法。
typescript
let notSure: any = 4;
notSure.ifItExists(); // Ok
notSure.toFixed(); // Ok
let prettySure: Object = 4;
prettySure.toFixed(); // ErrorVoid
空类型,常用于函数的返回值为 null 或 undefined,在严格模式下只能接收 undefined,非严格模式 下只能接收 null 和 undefined。
javascript
function foo (): void {
console.log('foo')
}Never
never 类型是任何类型的子类型,没有类型是 never 的子类型,因此除了 never 本身之外,没有任何 类型可以赋值给 never 类型,即使是 any。
javascript
// 错误
function error(message: string): never {
throw new Error(message);
}
// 死循环
function infiniteLoop(): never {
while (true) {}
}
// if 完整性检查
function getType (v: string | number) {
if (typeof v === 'string') {
return 'string'
} else if (typeof v === 'number') {
return 'number'
} else {
// 必须覆盖了所有的可能,target 的类型才为 never,因此类型不为 never 就表示漏写了分支
const t: never = v
}
}
// switch 完整性检查
interface Foo {
type: 'foo'
}
interface Bar {
type: 'bar'
}
interface Baz {
type: 'baz'
}
const getArea = (target: Foo | Bar | Baz) => {
switch (target.type) {
case 'foo': { return }
case 'bar': { return }
case 'baz': { return }
default: {
// 必须覆盖了所有的可能,target 的类型才为 never,因此类型不为 never 就表示漏写了
// 分支
const t: never = target
}
}
}
getArea({ type: 'baz' })Tuple
元组表示元素数量和类型都必须一一对应的数组
ts
let x: [string, number]
x = [10, 'hello'] // Error
x = ['hello'] // Error
x = ['hello', 10] // Ok
// 可以通过索引修改,类型要保证一致。
x[0] = 1 // Error
x[0] = 'bar' // Ok
// 不能通过索引添加
x[2] = 'bar' // Error
// 需要调用方法添加,类型需要满足 string | number。
x.push('bar') // OkEnum
枚举
数字枚举
javascript
// 默认情况下,从0开始为元素编号。
enum Color {Red, Green, Blue}
// { '0': 'Red', '1': 'Green', '2': 'Blue', Red: 0, Green: 1, Blue: 2 }
let c: Color = Color.Green; // output 1
// 也可以手动的指定成员的数值。从 1开始编号:
enum Color {Red = 1, Green, Blue}
// { '1': 'Red', '2': 'Green', '3': 'Blue', Red: 1, Green: 2, Blue: 3 }
let c: Color = Color.Green; // output 2
// 或者,全部都采用手动赋值:
enum Color {Red = 1, Green = 2, Blue = 4}
// { '1': 'Red', '2': 'Green', '4': 'Blue', Red: 1, Green: 2, Blue: 4 }
let c: Color = Color.Green; // output 2
// 当前成员的编号值 = 前一个成员的编号值 + 1
enum Color {Red = 1, Green = 3, Blue}
// { '1': 'Red', '3': 'Green', '4': 'Blue', Red: 1, Green: 3, Blue: 4 }
let c: Color = Color.Blue; // output 4
// 编号值也可以为小数和负数
enum Color {Red = 1.5, Green, Blue}
// { Red: 1.5, '1.5': 'Red', Green: 2.5, '2.5': 'Green', Blue: 3.5, '3.5': 'Blue' }
enum Color {Red = -1.5, Green, Blue}
// { Red: -1.5, '-1.5': 'Red', Green: -0.5, '-0.5': 'Green', Blue: 0.5, '0.5': 'Blue' }反向映射
数字枚举提供的一个便利是你可以由枚举的值得到它的名字,即反向映射,通过value映射key, 注意:只有数字枚举存在反向映射。
typescript
// 例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字:
enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2]; // output Green
// 数字重复时正向映射不受影响,反向映射会替换之前的。
enum Color {Red = 2, Green = 1, Blue}
// { '1': 'Green', '2': 'Blue', Red: 2, Green: 1, Blue: 2 }
let c: string = Color[2]; // output Blue松散性检查
typescript
enum Color {
Red,
Green,
Blue
}
function foo (arg: Color) {
console.log(arg)
}
foo(9) // Ok!字符串枚举
typescript
// 在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
enum Color {Red = 'My color is red', Green = 'My color is green', Blue = 'My color is blue'}
// {
// Red: 'My color is red',
// Green: 'My color is green',
// Blue: 'My color is blue'
// }异构枚举
typescript
// 枚举可以混合字符串和数字成员
enum Enum {
No = 0,
Yes = "YES",
Red = 10,
Green,
Blue,
}const枚举
为了避免在额外生成的代码上的开销和额外的非直接的对枚举成员的访问,我们可以使用 const枚举。 常量枚举通过在枚举上使用 const修饰符来定义。
typescript
const enum Enum {
A = 1,
B = A * 2
}常量枚举只能使用常量枚举表达式,并且不同于常规的枚举,它们在编译阶段会被删除。 常量枚举成员在使用的地方会被内联进来。 之所以可以这么做是因为,常量枚举不允许包含计算成员。
typescript
enum Gender {
Female,
Male
}
console.log(Gender.Female)
// >>>
var Gender;
(function (Gender) {
Gender[Gender["Female"] = 0] = "Female";
Gender[Gender["Male"] = 1] = "Male";
})(Gender || (Gender = {}));
console.log(Gender.Female);
// 普通枚举会通过代码转化成真实的对象,因此大量使用无疑会影响性能。由于ts是静态编译的,而const枚举的值在编译阶段是已知的,因此在编译阶段做值替换(即内联),减少最终生成代码的体积。
const enum Gender {
Female,
Male
}
console.log(Gender.Female)
// >>>
console.log(0 /* Female */);
const enum Gender {
Female,
Male,
// const枚举不能包含计算成员,会在编译阶段报错
Foo = 'Foo'.length
}外部枚举
外部枚举用来描述已经存在的枚举类型的形状。和const枚举一样,它在编译时是没有代码生成的。编译器通过这个声明会知道,有个存在于其他地方的枚举。
typescript
// 外部枚举和非外部枚举之间有一个重要的区别,在正常的枚举里,没有初始化方法的成员被当成常数成员。 对于非常数的外部枚举而言,没有初始化方法时被当做需要经过计算的。
declare enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
// >>>
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right]; // 需要经过计算的
declare const enum Directions {
Up,
Down,
Left,
Right
}
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
// >>>
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */]; // 常数成员常数项和计算所得项
常数项
每个枚举成员都带有一个值,它可以是 常量或 计算出来的。 当满足如下条件时,枚举成员被当作是常量:
它是枚举的第一个成员且没有初始化器,这种情况下它被赋予值 0:
typescript// E.X is constant: enum E { X }它不带有初始化器且它之前的枚举成员是一个 数字常量。 这种情况下,当前枚举成员的值为它上一个枚举成员的值加1。
typescript// All enum members in 'E1' and 'E2' are constant. enum E1 { X, Y, Z } enum E2 { A = 1, B, C }枚举成员使用 常量枚举表达式初始化。常数枚举表达式是TypeScript表达式的子集,它可以在编译阶段求值。当一个表达式满足下面条件之一时,它就是一个常量枚举表达式:
- 一个枚举表达式字面量(主要是字符串字面量或数字字面量)
- 一个对之前定义的常量枚举成员的引用(可以是在不同的枚举类型中定义的)
- 带括号的常量枚举表达式
- 一元运算符 +, -, ~其中之一应用在了常量枚举表达式
- 常量枚举表达式做为二元运算符 +, -, *, /, %, <<, >>, >>>, &, |, ^的操作对象。 若常数枚举表达式求值后为 NaN或 Infinity,则会在编译阶段报错。
计算所得项
所有其它情况的枚举成员被当作是需要计算得出的值。
typescript
enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = "123".length,
// 计算所得项后的枚举必须有初始值
M = 999
}联合枚举与枚举成员的类型
存在一种特殊的非计算的常量枚举成员的子集:字面量枚举成员。 字面量枚举成员是指不带有初始值的常量枚举成员,或者是值被初始化为
- 任何字符串字面量(例如: "foo", "bar", "baz")
- 任何数字字面量(例如: 1, 100)
- 应用了一元 - 符号的数字字面量(例如: -1, -100) 当所有枚举成员都拥有字面量枚举值时,它就带有了一种特殊的语义。 首先,枚举成员成为了类型! 例如,我们可以说某些成员只能是枚举成员的值:
typescript
enum ShapeKind {
Circle,
Square,
}
interface Circle {
kind: ShapeKind.Circle;
radius: number;
}
interface Square {
kind: ShapeKind.Square;
sideLength: number;
}
let c: Circle = {
kind: ShapeKind.Square,
// ~~~~~~~~~~~~~~~~ Error!
radius: 100,
}另一个变化是枚举类型本身变成了每个枚举成员的联合。 虽然我们还没有讨论联合类型,但你只要知道通过联合枚举,类型系统能够利用这样一个事实,它可以知道枚举里的值的集合。 因此,TypeScript能够捕获在比较值的时候犯的愚蠢的错误。 例如:
typescript
enum E {
Foo,
Bar,
}
function f(x: E) {
if (x !== E.Foo || x !== E.Bar) {
// ~~~~~~~~~~~
// Error! Operator '!==' cannot be applied to types 'E.Foo' and 'E.Bar'.
}
}
// 这个例子里,我们先检查 x是否不是 E.Foo。 如果通过了这个检查,然后 ||会发生短路效果, if语句体里的内容会被执行。 然而,这个检查没有通过,那么 x则 只能为 E.Foo,因此没理由再去检查它是否为 E.Bar。运行时的枚举
非const枚举在运行时是真正存在的对象。
typescript
enum E {
X, Y, Z
}
function f(obj: { X: number }) {
return obj.X;
}
// Works, since 'E' has a property named 'X' which is a number.
f(E);Array
类型 + [] 表示法
typescript
const list: number[] = [1, 2, 3]
list1.push("4") // Error!
const ary: (number | string)[] = [1, 'foo']泛型表示法
typescript
const list: Array<number> = [1, 2, 3]
const list: Array<number | string> = [1, 'foo']接口表示法
typescript
interface NumberArray {
[index: number]: number
}
const list: NumberArray = [1, 2, 3]接口常用来描述类数组
typescript
function foo () {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments
}
// 常用的类数组都有自己的接口定义,如 IArguments, NodeList, HTMLCollection 等:
function foo () {
let args: IArguments = arguments;
}
// IArguments是ts已经定义好的类型
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}解构数组
typescript
const input = [1, 2];
// 指示类型
function foo ([first, second]: [number, number]) {
console.log(first);
console.log(second);
}
foo(input);ts
let ary: (number | string)[] = ['foo', 10]
let ary2: Array<number | string> = ['foo', 10]Function
函数声明
typescript
function sum(x: number, y: number): number {
return x + y;
}
sum(1, 2) // Ok
// TypeScript 里传递给一个函数的参数个数必须与函数期望的参数个数一致。
sum(1) // Error
sum(1, 3) // Error函数表达式
typescript
const mySum = function (x: number, y: number): number {
return x + y;
};这是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 mySum,是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 mySum 添加类型,则应该是这样:
typescript
// 在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
const mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};完整函数类型
typescript
// 函数类型包含两部分:参数类型和返回值类型。 当写出完整函数类型的时候,这两部分都是需要的。 我们以参数列表的形式写出参数类型,为每个参数指定一个名字和类型。
const myAdd: (x: number, y: number) => number =
function(x: number, y: number): number { return x + y; };
// 这个名字只是为了增加可读性。 我们也可以这么写:
const myAdd: (baseValue: number, increment: number) => number =
function(x: number, y: number): number { return x + y; };
// 只要参数类型是匹配的,那么就认为它是有效的函数类型,而不在乎参数名是否正确。
// 对于返回值,其声明类型不为 void 或 any 的函数必须有返回值。
type MyAdd = (baseValue: number, increment: number) => number
const myAdd: MyAdd = (x: number, y: number): number => x + y可选参数和默认参数
JavaScript里,每个参数都是可选的,可传可不传。没传参的时候,它的值就是undefined。在TypeScript里我们可以在参数名旁使用 ? 实现可选参数的功能。
typescript
// lastName?: string 表示 lastName 的类型为 string | undefined
function buildName(firstName: string, lastName?: string) {
if (lastName)
return firstName + " " + lastName;
else
return firstName;
}
const result1 = buildName("Bob"); // <=> buildName("Bob", undefined)
const result2 = buildName("Bob", "Adams", "Sr."); // Error
const result3 = buildName("Bob", "Adams"); // Ok可选参数必须跟在必须参数后面。换句话说,可选参数后面不允许再出现必需参数。如果上例我们想让first name是可选的,那么就必须调整它们的位置,把first name放在后面。
在TypeScript里,我们也可以为参数提供一个默认值当用户没有传递这个参数或传递的值是undefined时,它们叫做有默认初始化值的参数。
typescript
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
const result1 = buildName("Bob"); // Ok
const result2 = buildName("Bob", undefined); // Ok
const result3 = buildName("Bob", "Adams", "Sr."); // Error
const result4 = buildName("Bob", "Adams"); // Ok
const result4 = buildName("Bob", 10); // Error => (firstName: string, lastName?: string): string默认值参数不受可选参数必须接在必需参数后面的限制,如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined值来获得默认值。
typescript
function buildName(x = 10, y: number) {
return x + y
}剩余参数
typescript
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
const employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");剩余参数会被当做个数不限的可选参数。可以一个都没有,同样也可以有任意个。
typescript
function buildName(firstName: string, ...restOfName: string[]) {
return firstName + " " + restOfName.join(" ");
}
const buildNameFun: (fname: string, ...rest: string[]) => string = buildName;this
重载
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。
typescript
// 实现一个函数 reverse,输入数字 123 的时候,输出反转的数字 321,输入字符串 'hello' 的时候,输出反转的字符串 'olleh'。
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}输入为数字的时候,输出也应该为数字,输入为字符串的时候,输出也应该为字符串。 而上面的返回值范围扩大了
typescript
// 重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现。
// TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
// 重载只能写函数声明,不能写函数表达式。
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}Object
object
object 表示非原始类型,也就是除 string,number,boolean,null,undefined,symbol, bigint 之外的类型。
typescript
function foo (o: object | null): void {
// ...
}
foo({ prop: 0 }); // OK
foo(null); // OK
foo(10); // Error
foo("string"); // Error
foo(false); // Error
foo(undefined); //Object
Object表示Object.prototype
TS把JS中的Object分成了两个接口来定义
Object接口用于定义JS Object的原型对象Object.prototype
typescript
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
hasOwnProperty(v: PropertyKey): boolean;
isPrototypeOf(v: Object): boolean;
propertyIsEnumerable(v: PropertyKey): boolean;
}ObjectConstructor接口用于定义 Object 自身的属性,如Object.create()
typescript
interface ObjectConstructor {
new(value?: any): Object;
(): any;
(value: any): any;
readonly prototype: Object;
create(o: object | null): any;
defineProperty<T>(o: T, p: PropertyKey, ...): T;
// ...
}{}
{}描述一个空对象,当你试图访问任何属性或方法时会产生编译错误,但可以访问 Object 类型上的所有属性/方法。
typescript
const o: object = {}
o.name // Error! => Property 'name' does not exist on type '{}'.比较
Object表示Object.prototype,所有对象类型都能够访问到Object中定义的属性/方法,由于拆箱装箱机制,可以被赋予原始值(除null 和 undefined),基本数据类型(除 null 和 undefined)可以访问到Object中定义的属性和方法,另外如果Object类型的值中的属性名与Object接口定义的属性冲突,则TS编译报错。
object表示非原始类型,可以访问到Object中定义的属性/方法,不能被赋予原始值,如果object类型的值中的属性名与Object接口定义的属性冲突,则以我们定义的为准。
{}表示没有任何属性的对象类型,可以被赋予原始值(除null 和 undefined),当你试图访问任何属性或方法时会产生编译错误,但可以访问到Object中定义的属性和方法。
typescript
let o1: {} = {}
o1 = 3 // Ok
let o2: Object = {}
o2 = 3 // Ok
let o3: object = {}
o3 = 3 // Error
const o4: Object = 'foo'
// Object类型的变量只是允许你给它赋任意值,但是却不能够在它上面调用任意的方法,即便它真的有这些方法:
o.toUpperCase() // Error解构对象
typescript
let o = {
a: "foo",
b: 12,
c: "bar"
};
let { a, b } = o;
// 指示类型
let { a, b }: { a: string, b: number } = ounknown
unknown 是 any 的安全类型,可以被任何类型赋值。
ts
const unknown: unknown = { name: 'foo '}
// 不能访问属性,即使它真的有。
unknown.name
// 没有传入类型时,默认会推导为 unknowun
// unknown 和其它类型联合都为 unknowun
// unknown 和其他类型交叉都为其它类型
// never 是 unknown 的子类型
type r = never extends unknown ? true : false
// keyof unknown 的结果为 never