深入浅出 Typescript 学习笔记
TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。
TypeScript 由微软开发的自由和开源的编程语言。
TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。
以上为网上对 Typescript 的一些解释,那我们为什么要学 Typescript?
提到前端我们首先会想到 HTML,CSS,JavaScript 三大家族,我们掌握这三个就可以在前端界获得一席之地,怎么突然又冒出个 Typescript,真心是学不动了,但是众所周知,Vue 创始人尤雨溪尤大大已经宣布 Vue3.x 代码库将使用 Typescript 编写,并且在知乎上对于"Typescript 不适合在 Vue 开发业务中使用吗?" 的提问中做出回答,传送门:https://www.zhihu.com/question/310485097/answer/591869966,Vue 又是现在国内主流的前端框架之一,只能说,如果不学 Typescript,尤大大都救不了你了。
众所周知,从本质上来说,JavaScript是一种自由松散语言,它的语法规则并不是那么严格。正因为如此,我们就更容易犯错,而且,即使是在运行的时候,我们也不能找到所有的错误。鉴于此,TypeScript作为JavaScript的增强版,它的语法更严格,我们在编写代码的时候就能够发现大部分错误。不仅如此,按照TypeScript官方的说法,TypeScript使得我们能够以JavaScript的方式实现自己的构思。TypeScript对面向对象的支持也非常完善,它拥有面向对象编程语言的所有特性。
TypeScript最大的目的是让程序员更具创造性,提高生产力,它将极大增强JavaScript编写应用的开发和调试环节,让JavaScript能够方便用于编写大型应用和进行多人协作。
不过目前最后运行时还需要将TypeScript编译为JavaScript。
那接下来我们就来看看如何安装使用 Typescript。
在安装 Typescript 之前我们要先安装 NodeJs,然后运行
npm install -g typescript
以上命令会在全局环境下安装 tsc 命令,安装完成之后,我们就可以在任何地方执行 tsc 命令了。
编译一个 TypeScript 文件很简单:
tsc demo.ts
运行上面的代码,我们就可以将 demo.ts 生成一个可以让浏览器解析的 demo.js 的文件。
我们约定使用 TypeScript 编写的文件以 .ts 为后缀,用 TypeScript 编写 React 时,以 .tsx 为后缀。
那么我们在编写代码的时候不能每次都手动编译 .ts 文件,我们想要的是实时编译 .ts 文件,接下来我们就以 webstorm 编辑器来使 .ts 文件进行实时编译。其他编辑器可自行百度如何实时编译。
webstorm 版本:
创建一个 demo 项目,然后在项目中创建一个 tsconfig.json 的文件,内容如下:
这里只是简单的配置,详细参数配置:https://www.tslang.cn/docs/handbook/tsconfig-json.html
打开Webstorm,为TypeScript文件更改编译设置,File->Settings->Tool->File Watchers->TypeScript,这里我们需要选择TypeScript,但是File Watchers下默认是不存在的。需要点击右侧“+”号,选择,弹出 New Watcher,设置好圈红线的部分,点击ok。勾选“TypeScript”,点击ok。
File->Settings->Languages & Frameworks->TypeScript
根据上面的操作我们就可以实时编译 .ts 文件了。
目录结构如下,在 index.html 中银润 test.js
test.ts 会实时编译为 test.js 文件。
test.ts
test.js
index.html 开发者工具中的打印日志
根据上面的步骤我们就可以对 ts 文件进行实时编译了,接下来我们就来看一下 Typescript 的一些基本用法。
从上图我们可以看出 Typescript 包含了 ES6 和 ES5,那我们就可以在 typescript 中使用 es5 和 es6,同时进行了扩展,语法上跟我们之前讲的 Java 语法有很多相似。
Typescript 基础类型
Typescript 基础类型有:布尔类型(boolean)、数字类型(number)、字符串类型(string)、数组类型(array)、元组类型(tuple)、枚举类型(enum)、任意类型(any)、null 和 undefined 、void、never 类型等,接下来我们就一一来看一下这些类型的应用。
复制代码
1 /**
2 * 在定义完参数进行赋值时,
3 * 必须按照给定的参数类型进行赋值
4 * 否则会出现编译问题
5 * 但在页面当中还是会进行编译
6 * 但是不提倡这么做
7 */
8 // boolean
9 let flag: boolean = true;
10 console.log(flag); // true
11 // let flag1:boolean = 123; // Type '123' is not assignable to type 'boolean'.
12
13 // number
14 let num: number = 123;
15 console.log(num); // 123
16
17 // string
18 let str: string = "abc";
19 console.log(str); // abc
20
21 // array 两种定义方式
22 let arrS: string[] = ["123", "abc"]; // 数组内元素必须为 string
23 let arrA: Array = [123, 456]; // 数组内元素必须为 number
24 console.log(arrS); // ["123", "abc"]
25 console.log(arrA); // [123, 456]
26
27 // tuple 元祖类型,属于数组的一种,可以为每个元素指定类型
28 let tup: [number, string] = [123, "abc"];
29 console.log(tup); // [123, "abc"]
30 // let tup1:[number,string] = ["abc",123]; // 报错
31
32 // enum 枚举类型
33 /**
34 * 在日常生活或者开发中
35 * 很多都不能或者不容易使用数据表达
36 * 如:颜色,日期,角色,性别等
37 * 例如在开发中我们常用 -1 表示 error,用 0 表示 success
38 * 这个就可以成为枚举
39 */
40 enum Flag {
41 error = -1,
42 success = 0
43 }
44
45 let e: Flag = Flag.error;
46 console.log(e); // -1
47
48 /**
49 * 如果枚举元素不赋值,则默认取值为下标
50 * 如果某个元素取值为数字 n
51 * 后面的元素如果不取值则默认为 n+1,以此类推
52 * 如果某个元素取值为 string 类型
53 * 后面的元素则必须取值,取值类型无要求
54 */
55 enum Color {
56 red, blue, black = 4, yellow, green = "green", white = 123
57 }
58
59 let red: Color = Color.red;
60 let blue: Color = Color.blue;
61 let black: Color = Color.black;
62 let yellow: Color = Color.yellow;
63 let green: Color = Color.green;
64 let white: Color = Color.white;
65 console.log(red, blue, black, yellow, green, white); // 0 1 4 5 "green" 123
66
67 // undefined
68 let un: undefined;
69 console.log(un); // undefined
70
71 // 我们也可以通过 | 来赋值多种类元素
72 let uns: number | undefined;
73
74 // any 类型,可以为任意类型
75 let an: any = 123;
76 console.log(an); // 13
77 an = "abc";
78 console.log(an) // abc
复制代码
在上面的代码中,我们演示了一下 Typescript 中的一些基本类型的使用,接下来我们再来看一下在函数中数据类型的应用。
Typescript 函数方法
复制代码
1 // 如果没有返回值,则在方法名后面加 :void
2 function test(): void {
3 console.log("test")
4 }
5
6 test(); // test
7
8 // 如果有返回值,则在方法名后面加 :返回值的类型
9 function num(): number {
10 return 123;
11 }
12
13 console.log(num()); // 123
14
15 function str(): string {
16 return "abc";
17 }
18
19 console.log(str()); // "abc"
20
21 // 定义传参
22 function getData(name: string, age: number): void {
23 console.log(`${name}--${age}`)
24 }
25
26 getData("张三", 18); // 张三--18
27 // getData("张三"); // 报错 Expected 2 arguments, but got 1.
28 // getData("张三","18"); // 报错 Argument of type '"18"' is not assignable to parameter of type 'number'.
29
30 /**
31 * 方法可选参数
32 * 在参数后面添加 ?
33 * 表示该参数为可选参数
34 */
35 function getInfo(name: string, age?: number): string {
36 if (age) {
37 return `${name}--${age}`
38 } else {
39 return `${name}`
40 }
41 }
42
43 console.log(getInfo("张三", 18)); // 张三--18
44 console.log(getInfo("张三")); // 张三
45
46 /**
47 * 方法默认参数
48 * 在参数后面直接赋值
49 * 表示该参数直接当做了被传入参数
50 */
51 function getUser(name: string, age: number = 18): string {
52 if (age) {
53 return `${name}--${age}`
54 } else {
55 return `${name}`
56 }
57 }
58
59 console.log(getUser("张三", 18)); // 张三--18
60 console.log(getUser("张三")); // 张三--18
61
62 /**
63 * 剩余参数
64 * 如果在传参过程中
65 * 前面的参数已经给定
66 * 在调用函数传参时会先将
67 * 传入的参数作为指定参数
68 * 剩余参数必须为最后一个参数传入
69 */
70 // 正常的传参
71 function sum1(...arr: number[]): number {
72 let sum: number = 0;
73 for (let i = 0; i < arr.length; i++) {
74 sum += arr[i];
75 }
76 return sum;
77 }
78 console.log(sum1(1, 2, 3, 4)); // 10
79
80 // a 作为第一个参数,其余的为剩余参数,即 (a,剩余参数)
81 function sum2(a: number, ...arr: number[]): number {
82 let sum: number = 0;
83 for (let i = 0; i < arr.length; i++) {
84 sum += arr[i];
85 }
86 return sum;
87 }
88 console.log(sum2(1, 2, 3, 4)); // 10
复制代码
在上面的代码中,我们实现了在 ts 中如何定义方法和如何进行方法传参,跟定义基本类型一样需要对方法进行有效的规定。
接下来我们再来看一下在 ts 中如何实现类和类的继承。
Typescript 类
在 ES5 中,我们是通过构造方法和原型链的方式进行继承的,在之前的文章中我们也讲过如何实现继承,传送门:https://www.cnblogs.com/weijiutao/p/12090916.html,Typescript 包含 ES6,所以本章着重讲解一下 ES6 中 class 关键字的类和继承。
复制代码
1 // 在 ts 中定义类
2 class Person {
3 name: string;
4
5 constructor(name: string) {
6 this.name = name;
7 }
8
9 getName(): void {
10 console.log(this.name);
11 }
12
13 setName(name: string): string {
14 return this.name = name;
15 }
16
17 work(): void {
18 console.log("父类在工作")
19 }
20 }
21
22 let p = new Person("张三");
23 p.getName(); // 张三
24 p.setName("李四");
25 p.getName(); // 李四
26 p.work(); // 父类在工作
27
28 // 在 ts 中实现继承
29 /**
30 * 通过 extends 继承了 Person 的属性和方法
31 */
32 class Student extends Person {
33 constructor(name: string) {
34 super(name);
35 }
36
37 // 子类自己的方法
38 run(): void {
39 console.log(this.name + "在运动")
40 }
41
42 // 子类重写父类的方法
43 work(): void {
44 console.log("子类在工作")
45 }
46
47 }
48
49 let s = new Student("王五");
50 s.getName(); // 王五
51 s.run(); // 王五在运动
52 s.work(); // 子类在工作
复制代码
* 类里面的修饰符
* Typescript 里面定义属性的时候
* 给我们提供了三种修饰符
* public:公有类型,在类里面、子类、类外面都可以访问
* protected:保护类型,在类里面,子类里面可以访问,类外面无法访问
* private:私有类型,在类里面可以访问,子类,类外面无法访问
* 属性不加修饰符,默认为公有属性
复制代码
1 /**
2 * 类里面的修饰符
3 * Typescript 里面定义属性的时候
4 * 给我们提供了三种修饰符
5 * public:公有类型,在类里面、子类、类外面都可以访问
6 * protected:保护类型,在类里面,子类里面可以访问,类外面无法访问
7 * private:私有类型,在类里面可以访问,子类,类外面无法访问
8 * 属性不加修饰符,默认为公有属性
9 */
10
11 class Person {
12 name: string;
13 public age: number = 18;
14 protected sex: string = "男";
15 private city: string = "北京";
16
17 constructor(name: string) {
18 this.name = name;
19 }
20
21 // 在本类中访问 public 类型
22 getName(): void {
23 console.log(this.name);
24 }
25
26 // 在本类中访问 public 类型
27 getAge(): void {
28 console.log(this.age);
29 }
30
31 // 在本类中访问 protected 类型
32 getSex(): void {
33 console.log(this.sex);
34 }
35
36 // 在本类中访问 private 类型
37 getCity(): void {
38 console.log(this.city);
39 }
40
41 }
42
43 let p = new Person("张三");
44 // 外部访问 public 类型
45 console.log(p.name); // 张三
46 // 外部访问 public 类型
47 console.log(p.age); // 18
48 // 外部访问 protected 类型
49 // console.log(p.sex); // 报错 Property 'sex' is protected and only accessible within class 'Person' and its subclasses.
50 // 外部访问 private 类型
51 // console.log(p.city); // 报错 Property 'city' is private and only accessible within class 'Person'.
52
53 class Student extends Person {
54 constructor(name: string) {
55 super(name);
56 }
57
58 getInfo(): void {
59 // 在子类中访问 public 属性
60 console.log(this.name);
61 // 在子类中访问 private 属性
62 console.log(this.age);
63 // 在子类中访问 protected 属性
64 console.log(this.sex);
65 // 在子类中访问 private 属性
66 // console.log(this.city) // 报错 Property 'city' is private and only accessible within class 'Person'.
67 }
68 }
69
70 let s = new Student("王五");
71 s.getInfo(); // 王五 18 男
复制代码
在上面的代码中我们实现了一下 class 中的修饰符,接下来我们再来看一下 class 的静态属性、静态方法
复制代码
1 /**
2 * 通过 static 关键字可以定义静态属性和静态方法
3 * 静态属性和静态方法直接通过 类. 来实现
4 */
5
6 class Person {
7 name: string;
8 static age: number = 18;
9
10 constructor(name: string) {
11 this.name = name;
12 }
13
14 run(): void {
15 console.log(`${this.name}在运动`)
16 }
17
18 /**
19 * 静态方法无法通过 this 调用类里面的属性
20 * 通过 类. 调用
21 */
22 static work(): void {
23 console.log("父类静态方法" + Person.age)
24 }
25 }
26
27 let p = new Person("张三");
28 p.run(); // 张三在运动
29 console.log(p.name); // 张三
30
31 console.log(Person.age); // 18
32 Person.work(); // 父类静态方法18
复制代码
在上面的代码中我们实现了一下 class 中的静态属性、静态方法,接下来我们再来看一下 class 的多态
复制代码
1 /**
2 * 我们在定义类方法后
3 * 子类继承该类,并根据实际情况
4 * 来实现自己所需要的类方法
5 */
6 class Person {
7 name: string;
8 age: number;
9
10 constructor(name: string) {
11 this.name = name;
12 }
13
14 work(): void {
15 console.log(`${this.name}在工作`)
16 }
17
18
19 }
20
21 class Student extends Person {
22 constructor(name: string) {
23 super(name);
24 }
25
26 run(): void {
27 console.log(`${this.name}在学习`)
28 }
29 }
30 let s = new Student("张三");
31 s.run(); // 张三在学习
32
33 class Teacher extends Person {
34 constructor(name: string) {
35 super(name);
36 }
37
38 run(): void {
39 console.log(`${this.name}在讲课`)
40 }
41 }
42 let t = new Teacher("李四");
43 t.run(); // 李四在讲课
复制代码
在上面的代码中,我们定义了 Person 类并定义了一个 work 方法,然后 Student 和 Teacher 类分别继承了 Person,但是根据自己的角色重写了 work 方法,这就是一种多态。
接下来我们再来看一下 class 中的抽象类
复制代码
1 /**
2 * 抽象类是提供其他类继承的基类,不能被实例化
3 * abstract 关键字定义抽象类和抽象方法
4 * 子抽象类中的抽象方法不能包含具体实现,必须在实现类中实现
5 * 抽象类和抽象方法只是用来定义标准
6 */
7 abstract class Person {
8 name: string;
9
10 constructor(name: string) {
11 this.name = name;
12 }
13
14 abstract work(): any;
15 }
16
17 // 无法被实例化
18 // let p = new Person(); // 报错 error TS2511: Cannot create an instance of an abstract class.
19
20 class Student extends Person {
21 constructor(name: string) {
22 super(name);
23 }
24
25 work(): any {
26 console.log(`${this.name}在学习`)
27 }
28 }
29
30 let s = new Student("张三");
31 s.work(); // 张三在学习
复制代码
Typescript 接口
在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,几口是一种限制和规范的作用。接口定义了某一批类所需要遵循的规范,接口不关心这些类的内部状态数据,也不关心这些类里面方法的实现细节,它只规定这批类里面必须提供某些方法,提供这些方法的类就可以满足实际需要,Typescript 中的接口类似于 Java,同时还增加了更灵活的接口类型,包括属性、函数、数组类等。
在日常生活中,我们会接触到很多类似接口的问题,比如 USB 接口,我们在电脑上插鼠标,键盘,U盘的时候不用去考虑它到底能不能插进去,只要型号对了就肯定能插进去,接口就相当于一个标准,你要想把鼠标插到我的电脑上,在出厂时就必须遵守该电脑定义的接口标准。
接下来我们就来看一下接口:
1、属性和类接口
复制代码
1 interface FullName {
2 firstName:string,
3 lastName:string,
4 sayHi: ()=>string
5 }
6
7 let person:FullName = {
8 firstName:"张",
9 lastName:"三",
10 sayHi: ():string =>{return "hell world"}
11 };
12
13 console.log(person.firstName); // 张
14 console.log(person.lastName); // 三
15 console.log(person.sayHi()); // hello world
复制代码
在上面的代码中,我们通过 interface 编写了一个 FullName 的接口,然后定义了一个 person 的变量来实现这个接口,那么我们就可以使用该接口里面的属性了。
2、函数类型接口
复制代码
1 // 对方法传入的参数以及返回值进行约束
2 interface encrypted {
3 (key: string, value: string): string
4 }
5
6 // key 和 value 必须符合 encrypted 接口的 string 类型约束
7 let getData: encrypted = (key: string, value: string) => {
8 return `${key}--${value}`;
9 };
10 // 错误写法
11 // let getData:encrypted = (key:number,value:string)=>{
12 // return `${key}--${value}`;
13 // };
14 console.log(getData("name", "张三")); // name--张三
复制代码
3、数组类型接口
复制代码
1 interface nameArray {
2 [index:number]:string
3 }
4
5 let list:nameArray = ["张三","李四"];
6 // let list:namelist = ["张三","李四",123]; // 错误元素 123 不是 string 类型
复制代码
4、接口的继承
复制代码
1 /**
2 * 通过 implements 来实现接口的继承
3 * 同时接口内的属性和方法在子类中必须重新定义
4 * 可以实现多继承,class 子类 implements 接口1, 接口2{ }
5 */
6
7 interface Person {
8 name: string,
9 work: () => void
10 }
11
12 class Student implements Person {
13 name: string;
14
15 constructor(name: string) {
16