1、概述
1)教程
- 文档1:https://gitee.com/jiuyueqi/class#typescrip
- 文档2:简介 · TypeScript 入门教程 (xcatliu.com)
2)设计理念
- 在完整保留 JavaScript 运行时行为的基础上,通过引入静态类型系统来提高代码的可维护性,减少可能出现的 bug。
3)类型系统
- 类型系统按照「类型检查的时机」来分类,可以分为动态类型和静态类型。
- 动态类型是指在运行时才会进行类型检查,这种语言的类型错误往往会导致运行时错误。JavaScript 是一门解释型语言,没有编译阶段,所以它是动态类型。
- 静态类型是指编译阶段就能确定每个变量的类型,这种语言的类型错误往往会导致语法错误。TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查。
2、数据类型
1)原始数据类型
(1)数值
-
使用
number
定义数值类型。- 其中
0b1010
和0o744
是 ES6 中的二进制和八进制表示法,它们会被编译为十进制数字。 - NaN 表示非数值, Infinity 表示无穷大。
let decLiteral: number = 6; let hexLiteral: number = 0xf00d; // ES6 中的二进制表示法 let binaryLiteral: number = 0b1010; // ES6 中的八进制表示法 let octalLiteral: number = 0o744; let notANumber: number = NaN; let infinityNumber: number = Infinity;
- 其中
(2)字符串
-
使用
string
定义字符串类型。let myName: string = 'Tom'; let myAge: number = 25; // 模板字符串 let sentence: string = `Hello, my name is ${myName}. I'll be ${myAge + 1} years old next month.`;
(3)布尔值
-
在 TypeScript 中,使用
boolean
定义布尔值类型。let isDone: boolean = false;
-
注意,使用构造函数
Boolean
创造的对象不是布尔值,而是一个Boolean
对象。let createdByNewBoolean: Boolean = new Boolean(1);
(4)null 和 undefined
-
在 TypeScript 中,可以使用
null
和undefined
来定义这两个原始数据类型。let u: undefined = undefined; let n: null = null;
-
与
void
的区别是,undefined
和null
是所有类型的子类型。也就是说undefined
类型的变量,可以赋值给number
类型的变量。// 这样不会报错 let num: number = undefined;
2)原语
(1)BigInt
-
bigint 非常大的整数
const one:bigint = BigInt(100)
(2)symbol
-
symbol 全局唯一引用
const first = Symbol("name") const second = Symbol("name") console.log(first === second) // false
3)对象
- 带有任何属性的值都可以看成是对象。
4)数组
(1)定义
【1】类型 + 方括号
-
type []
表示数组,数组的项中不允许出现其他的类型。let fibonacci: number[] = [1, 1, 2, 3, 5]; // success let fibonacci: number[] = [1, '1', 2, 3, 5]; // Type 'string' is not assignable to type 'number'.
-
数组的一些方法的参数也会根据数组在定义时约定的类型进行限制。
let fibonacci: number[] = [1, 1, 2, 3, 5]; fibonacci.push('8'); // Argument of type '"8"' is not assignable to parameter of type 'number'.
-
一个比较常见的做法是,用
any
表示数组中允许出现任意类型。let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];
【2】数组泛型
-
使用数组泛型
Array< elemType >
来表示数组:let fibonacci: Array<number> = [1, 1, 2, 3, 5];
(2)元组
-
元组类型其实是数组类型的扩展,用于表示已知元素数量和类型的数组,各元素的类型不必相同,但对于位置的类型需要相同。
let tup1: [string, number, boolean]; tup1 = ['宋祖儿', 100, true]; tup1 = ['zhang', 200, 200]; // 报错
-
定义一对值分别为
string
和number
的元组,可以只赋值其中一项。let tom: [string, number]; tom[0] = 'Tom';
-
但是当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。
let tom: [string, number]; tom = ['Tom', 25]; let tom1: [string, number]; tom1 = ['Tom']; // Property '1' is missing in type '[string]' but required in type '[string, number]'.
-
当添加越界元素时,它的类型会被限制为元组中每个类型的联合类型。
let tom: [string, number]; tom = ['Tom', 25]; tom.push('male'); tom.push(true); // Argument of type 'true' is not assignable to parameter of type 'string | number'.
5)枚举
- 枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。
- 枚举底层实现的本质其实就是数值类型, 所以赋值一个数值不会报错。
(1)定义
-
枚举使用
enum
关键字定义。enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
-
枚举成员会被赋值为从
0
开始递增的数字,同时也会对枚举值到枚举名进行反向映射:enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 0); // true console.log(Days["Mon"] === 1); // true console.log(Days["Sat"] === 6); // true console.log(Days[0] === "Sun"); // true console.log(Days[1] === "Mon"); // true console.log(Days[6] === "Sat"); // true
-
虽然默认是从0开始递增的, 但是我们也可以手动的指定枚举的取值的值。如果手动指定了前面枚举值的取值, 那么后面枚举值的取值会根据前面的值来递增。
enum Gender2 { Male = 5, Femal } console.log(Gender2.Male); // 5 console.log(Gender2.Femal);// 6
-
如果手动指定了后面枚举值的取值, 那么前面枚举值的取值不会受到影响。
enum Gender3 { Male, Femal = 10 } console.log(Gender3.Male); // 0 console.log(Gender3.Femal);// 10
-
手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为
1
:enum Days {Sun = 7, Mon = 1.5, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 7); // true console.log(Days["Mon"] === 1.5); // true console.log(Days["Tue"] === 2.5); // true
-
如果未手动赋值的枚举项与手动赋值的重复了,后者进行覆盖,TypeScript 是不会察觉到这一点的。
enum Days {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat}; console.log(Days["Sun"] === 3); // true console.log(Days["Wed"] === 3); // true console.log(Days[3] === "Sun"); // false console.log(Days[3] === "Wed"); // true
(2)计算所得项
-
枚举项有两种类型:常数项和计算所得项。前面我们所举的例子都是常数项,一个典型的计算所得项的例子:
enum Color {Red, Green, Blue = "blue".length};
-
在计算所得项后面的是未手动赋值的项,那么它就会因为无法获得初始值而报错:
enum Color {Red = "red".length, Green, Blue}; // index.ts(1,33): error TS1061: Enum member must have initializer. // index.ts(1,40): error TS1061: Enum member must have initializer.
(3)常数枚举
-
常数枚举是使用
const enum
定义的枚举类型。 -
常数枚举与普通枚举的区别是,它会在编译阶段被删除,并且不能包含计算成员。
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 */];
(4)外部枚举
- 外部枚举(Ambient Enums)是使用
declare enum
定义的枚举类型。 declare
定义的类型只会用于编译时的检查,编译结果中会被删除。
(5)枚举类型
-
数字枚举
1.数字枚举的取值可以是字面量, 也可以是常量, 也可以是计算的结果
2.如果采用字面量对第一个成员进行赋值,下面的成员会自动递增
3.如果采用常量或计算结果进行赋值,则下面的成员也必须初始化
-
字符串枚举
1.如果采用字面量对第一个成员进行赋值,下面的成员也必须赋值
2.采用[index]的形式不能获取到内容,需要传入[key]
3.字符串枚举不能使用常量或者计算结果给枚举值赋值
4.它可以使用内部的其它枚举值来赋值
enum Direction { up = "UP", down = "DOWN" } console.log(Direction.up); console.log(Direction.down); console.log(Direction[0]); // 报错 console.log(Direction["up"]); // UP const val = "金晨"; const res = () => "王鸥"; enum User { // a = val, // 报错 // b = res() // 报错 c = "HTML", d = c }
-
异构枚举:枚举中既包含数字又包含字符串, 我们就称之为异构枚举
1.如果是字符串枚举, 那么无法通过原始值获取到枚举值
enum Gender{ Male = 1, Female = '女' } console.log(Gender.Male); console.log(Gender.Female); console.log(Gender[1]); console.log(Gender['女']);
-
把枚举成员当做类型来使用
enum Gender{ Male , Female } interface ITestInterface { age: Gender // age: (Gender.Male | Gender.Female) } class Person implements ITestInterface{ // age: Gender.Male age: Gender.Female }
6)never 类型
-
表示的是那些永不存在的值的类型。
-
可以应用于 switch 语句,限制扩展接口导致异常错误。
-
never 类型是那些总是会抛出异常的函数的返回值类型。
function error(message: string): never {
throw new Error(message)
}
-
返回 never的函数必须存在无法到达的终点。
function infiniteLoop(): never { while (true) { } }
7)unknown 类型
(1)定义
- unknown 类型代表任何值。这与 any 类型类似,但更安全,因为对未知 unknown 值做任何事情都不合法
- unknown 类型不能调用属性和方法。
- unknown 类型被称作安全的any
(2)特性
-
任何类型都可以赋值给unknown类型
// 1.任何类型都可以赋值给unknown类型 let str:unknown; str = 18; str = "张馨予"; str = false;
-
不能将unknown类型赋值给其它类型,只有自身和 any
// 2.不能将unknown类型赋值给其它类型 let val:unknown = 18; let num: number; num = val; // 报错
-
unknown与其它任何类型组成的交叉类型最后都是其它类型,即交集更加集体,只有其他类型
// 3.unknown与其它任何类型组成的交叉类型最后都是其它类型 type MyType1 = number & unknown; type MyType2 = unknown & boolean; let a:MyType1 = 18; let b:MyType2 = true;
-
unknown除了与any以外, 与其它任何类型组成的联合类型最后都是unknown类型,即并集更加模糊,只有 unknown 类型
// 4.unknown除了与any以外, 与其它任何类型组成的联合类型最后都是unknown类型 type MyType3 = unknown | any; type MyType4 = unknown | number; type MyType5 = unknown | string | boolean;
-
never类型是unknown类型的子类型
// 5.never类型是unknown类型的子类型 type MyType6 = never extends unknown ? true : false;
8)map 类型
(1)定义
- Map 对象保存键值对,任何值(对象或者原始值) 都可作为一个键或一个值,并且能够记住键的原始插入顺序
- Map 是 ES6 中引入的一种新的数据结构,可以使用for of 进行迭代。
(2)属性
-
map.clear() – 移除 Map 对象的所有键/值对 。
-
map.set() – 设置键值对,返回该 Map 对象。
-
map.get() – 返回键对应的值,如果不存在,则返回 undefined。
-
map.has() – 返回一个布尔值,用于判断 Map 中是否包含键对应的值。
-
map.delete() – 删除 Map 中的元素,删除成功返回 true,失败返回 false。
-
map.size – 返回 Map 对象键/值对的数量。
-
map.keys() - 返回一个 Iterator 对象, 包含了 Map 对象中每个元素的键 。
-
map.values() – 返回一个新的Iterator对象,包含了Map对象中每个元素的值 。
let nameSiteMapping = new Map(); // 设置 Map 对象 nameSiteMapping.set("邱淑贞", 1); nameSiteMapping.set("宋茜", 2); nameSiteMapping.set("景甜", 3); // 获取键对应的值 console.log(nameSiteMapping.get("Runoob")); // 2 // 判断 Map 中是否包含键对应的值 console.log(nameSiteMapping.has("Taobao")); // true console.log(nameSiteMapping.has("Zhihu")); // false // 返回 Map 对象键/值对的数量 console.log(nameSiteMapping.size); // 3 // 删除 Runoob console.log(nameSiteMapping.delete("Runoob")); // true console.log(nameSiteMapping); // 移除 Map 对象的所有键/值对 nameSiteMapping.clear(); // 清除 Map console.log(nameSiteMapping); // 迭代 Map 中的 key for (let key of nameSiteMapping.keys()) { console.log(key); } // 迭代 Map 中的 value for (let value of nameSiteMapping.values()) { console.log(value); } // 迭代 Map 中的 key => value for (let entry of nameSiteMapping.entries()) { console.log(entry[0], entry[1]); } // 使用对象解析 for (let [key, value] of nameSiteMapping) { console.log(key, value); }
9)索引类型
-
我们可以使用一个索引访问类型来查询另一个类型上的特定属性
-
注意点: 不会返回null/undefined/never类型
xxxxxxxxxx class Person { name: string age: number}type MyIndex = Person["name"];let a:MyIndex = "赵韩樱子";console.log(a);// 获取指定对象, 部分属性的值, 放到数组中返回let obj = { name:'吴倩', age:18, gender: true}function getValues<T, K extends keyof T>(obj:T, keys:K[]):T[K][] { let arr = [] as T[K][]; keys.forEach(key=>{ arr.push(obj[key]); }) return arr;}let res = getValues(obj, ['name', 'age']);console.log(res);// 不会返回null/undefined/never类型interface TestInterface { a:string, b:number, c:boolean, d:symbol, e:null, f:undefined, g:never}type MyType = TestInterface[keyof TestInterface];js
+10)工具类型
-
以一个类型为基础,根据它推断出新的类型。
-
infer 类型推断
export default {} // 假如想获取数组里的元素类型,如果是数组则返回数组中元素的类型,否则返回这个类型本身 type Id = number[]; type IName = string[]; type Unpacked<T> = T extends IName ? string : T extends Id ? number : T; type idType = Unpacked<Id>; // idType 类型为 number type nameType = Unpacked<IName>; // nameType 类型为string // 使用infer简化操作 type ElementOf<T> = T extends Array<infer E> ? E : T; type res1 = ElementOf<string[]>; // string type res2 = ElementOf<boolean>; // boolean // infer可以推断出联合类型 type Foo<T> = T extends { a: infer U; b: infer U } ? U : never; type T11 = Foo<{ a: string; b: number }>; // T11类型为 string | number
-
Readonly / Partial 关键字
interface IPerson { name: string; age: number; } // 表示只读 type ReadonlyRes = Readonly<IPerson>; // 表示可选 type PartialRes = Partial<IPerson>;
-
Record 映射类型
// 参数1是键,参数2是值。笛卡尔积式地创造新的类型。 type Name = "person" | "animal" ; type Person = { name: string age: number } type NewType = Record<Name, Person> let res: NewType = { person: { name: "唐艺昕", age: 18 }, animal: { name: "云梦", age: 0.3 } }
-
Pick 映射类型
// 将原有类型中的部分内容映射到新类型中 interface IInfo { name: string age: number } type PartProp = Pick<IInfo, "name"> let obj4:PartProp = { name: "韩雪" }
-
Required
构建一个由 Type 的所有属性组成的类型,设置为必填。与 Partial 相反
interface IPerson { name?: string; age?: number; } let res:IPerson = { name: "舒畅", } let res2: Required<IPerson> = { name: "舒畅", age: 18 }
-
Omit<Type, Keys>
通过从 Type 中选取所有属性,然后删除 Keys (属性名或属性名的联合)来构造一个类型。
interface Student { name: string; age: number score: number; sex: boolean; } type SProps = Omit<Student, "name"> let res3:SProps = { age: 18, score: 100, sex: false }
3、类型系统
1)类型推论
-
根据初始值进行类型推论
不用明确告诉编译器具体是什么类型, 编译器就知道是什么类型,根据初始化值自动推断。
// 等价于 let uanme: string = "陈乔恩"; let uname = "陈乔恩"; uname = "徐璐" // uname = 123; // 报错 // uname = true; // 报错 let uage; uage = 123; uage = true; // 等价于 let x: (number | null)[] = [0, 1, null]; let x = [0, 1, null]; // x = [18, 28, 38, null, true];
-
上下文类型推论
TypeScript类型推论也可能按照相反的方向进行。 这被叫做“按上下文归类”。按上下文归类会发生在表达式的类型与所处的位置相关时。
// 上下文类型推断 window.onmousedown = function (mouseEvent) { console.log(mouseEvent.button); };
2)任意值
-
普通类型在赋值过程中改变类型是不被允许的,但如果是
any
类型,则允许被赋值为任意类型。let myFavoriteNumber: any = 'seven'; myFavoriteNumber = 7;
-
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成
any
类型而完全不被类型检查。let something; something = 'seven'; something = 7;
3)联合类型
(1)定义
-
联合类型又称为 union 类型,表示取值可以为多种类型中的一种。
let myFavoriteNumber: string | number; myFavoriteNumber = 'seven'; myFavoriteNumber = 7; // success let myFavoriteNumber: string | number; myFavoriteNumber = true; // index.ts(2,1): error TS2322: Type 'boolean' is not assignable to type 'string | number'.
-
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型。
let myFavoriteNumber: string | number; myFavoriteNumber = 'seven'; console.log(myFavoriteNumber.length); // 5 myFavoriteNumber = 7; console.log(myFavoriteNumber.length); // 编译时报错 // index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.
(2)属性与方法
-
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们 只能访问此联合类型的所有类型里共有属性或方法。
-
例1,
length
不是string
和number
的共有属性,所以会报错。function getLength(something: string | number): number { return something.length; } // index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'. // Property 'length' does not exist on type 'number'.
4)类型断言
(1)定义
-
类型断言可以用来手动指定一个值的类型,即允许变量从一种类型更改为另一种类型。
-
通俗的说就是我相信我自己在定义什么类型。
// 方式一 <类型>值 let len = (<string>str).length; console.log(len); // 方式二 值 as 类型 let len = (str as string).length; console.log(len);
(2)用途
【1】父类断言为子类
-
当类之间有继承关系时,类型断言也是很常见。
class ApiError extends Error { code: number = 0; } class HttpError extends Error { statusCode: number = 200; } function isApiError(error: Error) { if (typeof (error as ApiError).code === 'number') { return true; } return false; }
-
上面的例子中,我们声明了函数
isApiError
,它用来判断传入的参数是不是ApiError
类型,为了实现这样一个函数,它的参数的类型肯定得是比较抽象的父类Error
,这样的话这个函数就能接受Error
或它的子类作为参数了。但是由于父类
Error
中没有code
属性,故直接获取error.code
会报错,需要使用类型断言获取(error as ApiError).code
。
【2】任何类型断言为 any
-
我们需要将
window
上添加一个属性foo
,但 TypeScript 编译时会报错,提示我们window
上不存在foo
属性。此时我们可以使用
as any
临时将window
断言为any
类型。window.foo = 1; // index.ts:1:8 - error TS2339: Property 'foo' does not exist on type 'Window & typeof globalThis'. (window as any).foo = 1;
-
需要注意的是,将一个变量断言为
any
可以说是解决 TypeScript 中类型问题的最后一个手段。它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用
as any
。
(3)限制
-
要使得
A
能够被断言为B
,只需要A
兼容B
或B
兼容A
即可,这也是为了在类型断言时的安全考虑,毕竟毫无根据的断言是非常危险的。 -
Cat
包含了Animal
中的所有属性,除此之外,它还有一个额外的方法run
。TypeScript 并不关心Cat
和Animal
之间定义时是什么关系,而只会看它们最终的结构有什么关系——所以它与Cat extends Animal
是等价的。interface Animal { name: string; } interface Cat { name: string; run(): void; } let tom: Cat = { name: 'Tom', run: () => { console.log('run') } }; let animal: Animal = tom; ---等价于 interface Animal { name: string; } interface Cat extends Animal { run(): void; }
-
那么也不难理解为什么
Cat
类型的tom
可以赋值给Animal
类型的animal
了——就像面向对象编程中我们可以将子类的实例赋值给类型为父类的变量。当
Animal
兼容Cat
时,它们就可以互相进行类型断言了。interface Animal { name: string; } interface Cat { name: string; run(): void; } function testAnimal(animal: Animal) { return (animal as Cat); } function testCat(cat: Cat) { return (cat as Animal); }
5)类型别名
-
使用
type
创建类型别名,类型别名常用于联合类型,以减少联合类型的命名长度。 -
接口只能声明对象,但类型别名可以声明基本数据类型,联合类型,数组等。
type a1 = string | number | number[]
-
类型别名允许扩展
type money = { y1: number } type money2 = money & { y2: number } let salary: money2 = { y1: 10, y2: 20 }
-
不允许出现同名的类型别名
type mygoddassName = { name: string } type mygoddassName = { name: number } // error : 标识符重复
4、接口
1)定义
-
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。
-
接口只能定义对象
-
定义的变量比接口多了或者少了一些属性都是不允许的。
interface Person { name: string; age: number; } let tom: Person = { name: 'Tom', age: 25 };
(1)可选属性
-
可选属性的含义是该属性可以不存在。
interface Person { name: string; age?: number; } let tom: Person = { name: 'Tom' };
-
这时仍然不允许添加未定义的属性。
interface Person { name: string; age?: number; } let tom: Person = { name: 'Tom', age: 25, gender: 'male' }; // Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
(2)索引签名 (任意属性)
-
索引签名即任意属性。
-
接口允许有任意的属性,可以使用如下方式。
interface Person { name: string; age?: number; [propName: string]: string; } let tom: Person = { name: 'Tom', gender: 'male' };
-
定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集。
interface Person { name: string; age?: number; [propName: string]: string; } let tom: Person = { name: 'Tom', age: 25, gender: 'male' }; // index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'. // Index signatures are incompatible. // Type 'string | number' is not assignable to type 'string'. // Type 'number' is not assignable to type 'string'.
-
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型。
interface Person { name: string; age?: number; [propName: string]: string | number; } let tom: Person = { name: 'Tom', age: 25, gender: 'male' };
(3)只读属性
-
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用
readonly
定义只读属性。interface Person { readonly id: number; name: string; age?: number; [propName: string]: any; } let tom: Person = { id: 89757, name: 'Tom', gender: 'male' }; tom.id = 9527; // index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
2)继承
-
接口允许继承
interface animal { name: string } interface Bear extends animal { eat: true } const b1: Bear = { name: 'xiong', eat: true }
(1)同名接口
-
允许出现同名接口,通过同名的接口向现有的类型添加字段,使用此接口创建的变量,需要拥有该接口的所有同名接口的属性。
interface MyWindow { count: number } interface MyWindow { title: number } const w: MyWindow = { count: 1, title: 1 }
(2)多继承
-
接口的定义允许同时继承于多个接口
interface IFatherMoney { m1: number } interface IMotherMoney { m2: number } interface ISon extends IFatherMoney, IMotherMoney { s: number } let money: ISon = { m1: 100, m2: 100, s: 100 }
3)调用签名
-
调用签名,指使用接口表示函数类型时,参数列表和返回值类型的函数定义。
interface ImakeMoney { (salary: number, reward: number): number } let sum: ImakeMoney = function (x: number, y: number): number { return x + y; } let res = sum(10, 20);
5、函数
1)定义
-
函数定义的方式
// 匿名函数 const makeMoney = function(salary: number, reward: number): number { return salary + reward } // 有名函数 | 命名函数 | 普通函数 function writeCode(hour: number, sleep: number) { return hour } // 箭头函数 const seeMeiMei = (time: number):void => { console.log(`我每天要看${time}个小时MeiMei`); } seeMeiMei(8) // 接口函数 type myFunc = (x: number, y: number) => number const myfunc:myFunc = (a: number, b: number) => a + b
2)参数
(1)可选参数
-
可选参数后面不允许再出现必需参数。
function buildName(firstName: string, lastName?: string) { if (lastName) { return firstName + ' ' + lastName; } else { return firstName; } } let tomcat = buildName('Tom', 'Cat'); let tom = buildName('Tom');
(2)默认参数
-
在 ES6 中,我们允许给函数的参数添加默认值,TypeScript 会将添加了默认值的参数识别为可选参数。
function buildName(firstName: string, lastName: string = 'Cat') { return firstName + ' ' + lastName; } let tomcat = buildName('Tom', 'Cat'); let tom = buildName('Tom');
-
此时就不受「可选参数必须接在必需参数后面」的限制。
function buildName(firstName: string = 'Tom', lastName: string) { return firstName + ' ' + lastName; } let tomcat = buildName('Tom', 'Cat'); let cat = buildName(undefined, 'Cat');
(3)剩余参数
-
ES6 中,可以使用
...rest
的方式获取函数中的剩余参数(rest 参数)function push(array, ...items) { items.forEach(function(item) { array.push(item); }); } let a: any[] = []; push(a, 1, 2, 3);
-
事实上,
items
是一个数组。所以我们可以用数组的类型来定义它。function push(array: any[], ...items: any[]) { items.forEach(function(item) { array.push(item); }); } let a = []; push(a, 1, 2, 3);
3)函数重载
-
重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。
-
参数类型不同:
function disp(string):void; function disp(number):void;
参数数量不同:
function disp(n1:number):void; function disp(x:number,y:number):void;
参数类型顺序不同:
function disp(n1:number,s1:string):void; function disp(s:string,n:number):void;
-
注意,TypeScript 会优先从最前面的函数定义开始匹配,所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
// 定义函数重载 function addFunc(a:number, b: number):number; function addFunc(a:string, b: string):string; function addFunc(a:number, b: string):string; function addFunc(a:string, b: number):string; // 使用函数重载 function addFunc(a: any, b: any):any { return a + b; } addFunc(10, 20); addFunc("谭松韵", "金晨"); addFunc(27, "白鹿"); addFunc("赵今麦", 19); // 定义参数类型与参数数量不同 function star(s1:string):void; function star(n1:number,s1:string):void; function star(x:any,y?:any):void { console.log(x); console.log(y); } star("王心凌"); star(1,"爱你");
4)特殊的函数返回值
-
如果使用类型别名,定义了一个返回值为void的类型, 我们在函数使用的时候,并非一定不能有返回值。
相反,如果我们在函数中写了返回值,我们的返回值是有效的。
-
如果我们定义函数的时候明确指出,返回值为void,那么我们将除undefined 和 null 以外的值进行返回都会进行报错
type voidFunc = () => void let func1: voidFunc = function() { return true; } let func2: voidFunc = () => { return false; } let f1 = func1(); let f2 = func2(); console.log("f1: ", f1); console.log("f2: ", f2); // 注意点: 如果我们定义函数的时候明确指出,返回值为void, // 那么我们将除undefined 和 null 以外的值进行返回 function func3():void { // return 123 // 报错 }
+6、类
- 传统方法中,JavaScript 通过构造函数实现类的概念,通过原型链实现继承。
1)类的定义
-
定义类的关键字为 class,后面紧跟类名,类可以包含以下几个模块(类的数据成员):
- 字段 − 字段是类里面声明的变量。字段表示对象的有关数据。
- 构造函数 − 类实例化时调用,可以为类的对象分配内存。
- 方法 − 方法为对象要执行的操作。
class Person { // 注意点: 需要先定义实例属性,才能够使用 name: string age: number constructor(name: string, age: number){ this.name = name; this.age = age; } sayHello(): void{ console.log(`我的女神是${this.name}, 她今年${this.age}岁了, 但是在我心里她永远18岁!`); } } let p = new Person("邱淑贞", 54); p.sayHello(); // 定义类的同时,相当于定义了同名的接口。 let p1:Person={ myName:"", sayHello(){ ... } }
2)类的继承
-
类继承使用关键字 extends,子类除了不能继承父类的私有成员(方法和属性)和构造函数,其他的都可以继承。
-
TypeScript 一次只能继承一个类,不支持继承多个类,但 TypeScript 支持多重继承(A 继承 B,B 继承 C)。
class Person { name: string age: number constructor(name: string, age: number) { this.name = name; this.age = age; } say():void{ console.log(`我是${this.name}, 今年${this.age}岁`); } } class Student extends Person { score: string; constructor(name: string, age: number, score: string) { super(name, age); this.score = score; } say():void { // 调用父类的函数 super.say(); console.log(`我是重写之后的say方法, 我是学生${this.name}, 今年${this.age}岁了, 我的成绩为${this.score}`); } } let s = new Student("蒋依依", 18, "A"); s.say();
3)类的静态成员
-
static 关键字用于定义类的数据成员(属性和方法)为静态的,静态成员可以直接通过类名调用。
class StaticTest { static salary: string; static say(): void { console.log("我们想要的工资是: " + StaticTest.salary); } } StaticTest.salary = "18k"; StaticTest.say();
-
instanceof 运算符用于判断对象是否是指定的类型,如果是返回 true,否则返回 false。
// instanceof 运算符用于判断对象是否是指定的类型,如果是返回 true,否则返回 false。 class Person{} let p = new Person() let isPerson = p instanceof Person; console.log("p 对象是 Person 类实例化来的吗? " + isPerson); // true class Student extends Person {} let s = new Person() let isPerson2 = s instanceof Person; console.log("s 对象是 Person 类实例化来的吗? " + isPerson2); // true
4)类的修饰符
-
public(默认):公有,可以在任何地方被访问
-
protected: 受保护,可以被其自身以及其子类访问
-
private: 私有,只能被其定义所在的类访问。
-
readonly: 可以使用
readonly
关键字将属性设置为只读的。 只读属性必须在声明时或构造函数里被初始化。// readonly: 字段的前缀可以是 readonly 修饰符。这可以防止在构造函数之外对该字段进行赋值。 class PrintConsole { readonly str1: string = "HTML, CSS, JS, VUE REACT, NODE" readonly str2: string; readonly str3: string; readonly str4: string; constructor(str2: string, str3:string, str4:string) { this.str2 = str2; this.str3 = str3; this.str4 = str4; } }
5)类的存取器
-
通过 getters / setters 来截取对对象成员的访问
- 如果存在 get ,但没有 set ,则该属性自动是只读的
- 如果没有指定 setter 参数的类型,它将从 getter 的返回类型中推断出来
- 访问器和设置器必须有相同的成员可见性
class GetNameClass { private _fullName: string = "倪妮" get fullName():string { console.log("我是get方法"); return this._fullName } set fullName(newName:string) { console.log("我是set方法"); this._fullName = newName; } } let starname = new GetNameClass(); starname.fullName = "袁冰妍" // 自动调用 set 方法 console.log(starname.fullName); // 自动调用 get 方法
6)抽象类
-
定义
- 抽象类做为其它派生类的基类使用,不能被实例化。
- 抽象类是专门用于定义哪些不希望被外界直接创建的类的。
- 抽象类和接口一样用于约束子类。
-
抽象类和接口区别
- 抽象方法必须包含
abstract
关键字并且可以包含访问修饰符 - 接口中只能定义约束, 不能定义具体实现。而抽象类中既可以定义约束, 又可以定义具体实现
abstract class Person { abstract name: string; abstract show(): string; showName() { console.log(this.show()); } } class Student extends Person { name: string = "孟子义"; show():string { return "陈情令" } } // let p = new Person(); // 无法创建抽象类的实例 let s = new Student(); console.log(s.show());
- 抽象方法必须包含
7)类与接口
(1)类 实现 接口
-
类只能继承一个类,但可以同时实现多个接口。
- 实现接口,使用关键字 implements。
- 可以使用一个 implements 子句来检查一个类,是否满足了一个特定的接口。如果一个类不能正确地 实现它,就会发出一个错误。
interface IPersonInfo { name: string; age: number; sex?: string; show(): void; } interface IMusic { music: string } class Person implements IPersonInfo, IMusic { name: string = "吴谨言"; age: number = 32; music: string = "雪落下的声音"; show() { console.log(`${this.name}是'延禧攻略'的主演,她今年${this.age}岁了`); console.log(`${this.name}唱了一首歌叫 ${this.music}`); } } let p = new Person(); p.show(); // p.name = "周冬雨" // p.sex = "女" // 报错
(2)接口 继承 类
-
常见的面向对象语言中,接口是不能继承类的,但是在 TypeScript 中却是可以的。
-
只要一个接口继承了某个类, 那么就会继承这个类中所有的属性和方法。 但是只会继承属性和方法的声明, 不会继承属性和方法实现。
interface ITest extends Person { salary: number } // 类可以继承父类的同时,实现接口 class Star extends Person implements ITest { salary: number = 50; name: string = "关晓彤"; age: number = 18; } let s = new Star(); console.log(s.salary); console.log(s.name);
8)类的初始化顺序
- 基类的字段被初始化
- 基类构造函数运行
- 子类的字段被初始化
- 子类构造函数运行
+7、泛型
1)定义
-
通过用
< T >
来表示,放在参数的前面// 使用泛型 let getArray = <T>(value:T, items:number):T[]=>{ return new Array(items).fill(value); }; let arr = getArray<string>("刘亦菲", 3) // let arr = getArray<number>(10, 3) // 回顾 const techArr1: string[] = ["HTML", "CSS", "JS"]; const techArr2: Array<String> = ["VUE", "REACT", "ANGULAR"];
2)泛型-接口
-
将泛型与接口结合起来使用,可以大大简化我们的代码。
// 泛型接口 interface IPerson<T1, T2> { name: T1 sex: T2 } let p: IPerson<String, number> = { name: "于文文", sex: 0 }
-
泛型也可以使用默认值
interface IPerson<T1=String, T2=number> { name: T1 sex: T2 } let p: IPerson = { name: "于文文", sex: 0 }
3)泛型类
class Person<T1, T2> {
name: T1
age: T2
sex: T1
constructor(name: T1, age: T2, sex: T1) {
this.name = name
this.age = age
this.sex = sex
}
}
const p1 = new Person("黄诗诗", 18, "女")
const p2 = new Person<String, number>("虞书欣", 18, "女")
const p3:Person<String, number> = new Person("刘诗诗", 18, "女")
4)泛型约束
-
extend 字段限制了泛型可以接收的类型
function getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; } let x = { a: 1, b: 2}; getProperty(x, "a"); // getProperty(x, "c"); // 报错
8、兼容性
1)对象类型的兼容性
-
可多不可少
// 对象类型赋值给接口类型 // 注意点: 可多不可少 interface INameTest { name: string; } let n1 = {name: "祝绪丹"}; let n2 = {name: "江疏影", age: 18}; let n3 = {age: 18}; let val: INameTest; val = n1; val = n2; // val = n3;
-
会进行递归检查
// 注意点: 必须一一对应,会进行递归检查 interface ITestInfo { name:string; children:{ age:number }; } let p1 = {name:'吴宣仪', children:{age:18}}; let p2 = {name:'陈小纭',children:{age:true}}; let t:ITestInfo; t = p1; // t = p2;
2)函数类型的兼容性
-
参数个数
// 注意点: 可少不可多 /* let func1 = (a:number, b:number) => {}; let func2 = (x:number) => {}; func1 = func2; func2 = func1; */
-
参数类型
// 注意点: 参数类型必须相同 /* let func1 = (x:number)=>{}; let func2 = (x:number)=>{}; let func3 = (x:string)=>{}; func1 = func2; func2 = func1; func1 = func3; // 报错 func3 = func1; // 报错 */
-
参数返回值
// 注意点: 返回值类型必须相同 /* let func1 = ():number=> 18; let func2 = ():number=> 28; let func3 = ():string=> 'TS真好玩'; func1 = func2; func2 = func1; func1 = func3; // 报错 func3 = func1; // 报错 */
-
双向协变
-
1.参数的双向协变
let func1 = (x:(number | string)) =>{}; let func2 = (x:number) =>{}; func1 = func2; func2 = func1; // 报错
-
2.返回值双向协变
// 不能将返回值是联合类型的赋值给具体类型的 // 可以将返回值是具体类型的赋值给联合类型的 let func1 = (x:boolean):(number | string) => x ? 18 : '张含韵'; let func2 = (x:boolean):number => 28; func1 = func2; func2 = func1; // 报错
-
-
函数重载
// 不能将重载少的赋值给重载多的 // 可以将重载多的赋值给重载少 /* function add(x:number, y:number):number; function add(x:string, y:string):string; function add(x, y) { return x + y; } function sub(x:number, y:number):number; function sub(x, y) { return x - y; } // let fn = add; // fn = sub; // 报错 let fn = sub; fn = add; */
-
可选参数及剩余参数
// 当一个函数有剩余参数时,它被当做无限个可选参数 function func(args: any[], callback: (...args: any[]) => void) { } func([1, 2], (x, y, z) => console.log(x + ', ' + y + z)); func([1, 2], (x?, y?) => console.log(x + ', ' + y)); func([1, 2], (x, y?, z?) => console.log(x + ', ' + y));
3)枚举类型的兼容性
-
数字枚举与数字兼容
enum Gender{ Male, Female } let value:Gender; value = Gender.Male; value = 100;
-
数字枚举与数字枚举不兼容
enum Gender{ Male, // 0 Female // 1 } enum Animal{ Dog, // 0 Cat // 1 } let value:Gender; value = Gender.Male; value = Animal.Dog; // 报错
-
字符串枚举与字符串不兼容
enum Gender{ Male = '张若昀', Female = '唐艺昕' } let value:Gender; value = Gender.Male; value = Gender.Female; value = "娃嘻嘻" // 报错
4)类的兼容性
-
当比较一个类类型的两个对象时,只有实例的成员被比较。静态成员和构造函数不影响兼容性。
-
public: 可多不可少
class Animal { feet: number; age: number; constructor(name: string, numFeet: number) {} } class Size { feet: number; constructor(numFeet: number) {} } // 可多不可少 let a: Animal; let s: Size; s = a; // 正确 a = s; // 错误
-
private / protected: 不能互相赋值
class Animal { private feet: number; constructor(name: string, numFeet: number) {} } class Size { private feet: number; constructor(numFeet: number) {} } let a: Animal; let s: Size; s = a; // 错误 a = s; // 错误
5)泛型的兼容性
-
因为TypeScript是一个结构化的类型系统,类型参数只在作为成员类型的一部分被消耗时影响到结果类型。
interface Empty<T> {} let x: Empty<number>; let y: Empty<string>; x = y; // 正确,因为y符合x的结构 interface NotEmpty<T> { data: T; } let x: NotEmpty<number>; let y: NotEmpty<string>; x = y; // 错误,因为x和y不兼容