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<number> = [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         this.name = name;
17     }
18
19     work(): void {
20         console.log(`${this.name}在学习`)
21     }
22 }
23
24 let s = new Student("张三");
25 s.work(); // 张三在学习

5、接口的扩展

 1 /**
 2  * 通过 extends 来实现接口的扩展
 3  * 在实现接口的时候,接口的扩展接口也必须重新定义
 4  */
 5
 6 interface Animals {
 7     name: string,
 8 }
 9
10 interface Person extends Animals {
11     age: number,
12 }
13
14 let person:Person = {
15     name:"张三",
16     age: 18
17 };
18 console.log(person); // {name: "张三", age: 18}

Typescript 泛型

在软件工程中,我们不仅要创建一致的定义良好的 API,同时也要考虑可重用性,组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型,这在创建大型系统时为你提供了十分灵活的功能。

在 C# 和 Java 这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据,这样用户就可以有自己的数据类型来使用组件。

通俗理解,泛型就是解决函数、类、接口 的复用性,以及对不特定数据类型的支持。

1、泛型函数

现在需要写一个方法实现,我们传入的参数和返回的参数保持一致,并且参数必须为 string 或者 number,按照正常的思维我们代码应该是这样:

 1 function getData1(value: string): string {
 2     return value;
 3 }
 4
 5 function getData2(value: number): number {
 6     return value;
 7 }
 8
 9 // 返回 string 类型
10 getData1("abc");
11 // 返回 number 类型
12 getData2(123);

在上面的代码中,我们需要定义两个不同的方法来分别实现 string 和 number 类型数据的返回,这样会造成大量重复代码,当然我们可以将返回类型变为 any 类型来解决,如下:

1 function getData(value: any): any {
2     return value;
3 }
4
5 // 返回 string 类型
6 getData("abc");
7 // 返回 number 类型
8 getData(123);

使用 any 类型可以解决我们的问题,但是其实是放弃了类型检验,和普通 js 就没有什么区别了。

接下来我们来看一下泛型是如何解决该问题的:

 1 /**
 2  * 定义泛型类型 T
 3  * 我们需要 T 是什么类型
 4  * 直接定义 T 的类型即可
 5  * @param value
 6  */
 7 function getData<T>(value: T): T {
 8     return value;
 9 }
10
11 // 返回 string 类型
12 getData<string>("abc");
13 // 返回 number 类型
14 getData<number>(123);
15
16 // 错误写法
17 // getData<string>(456); // 报错 rgument of type ‘456‘ is not assignable to parameter of type ‘string‘.

在上面的代码中,我们通过 T 来定义方法的泛型,这样方法在传入时需要先定义参数的类型,然后定义返回数据类型,这样就实现了上面的问题。

2、泛型类

现在有这样一个问题,我们要定义一个类,该类中 toString 方法返回值为一个二维坐标系上的某个点,按照之前我们讲的类的内容,可以写出如下代码:

 1 class Point {
 2     x: number;
 3     y: number;
 4
 5     constructor(x: number, y: number) {
 6         this.x = x;
 7         this.y = y;
 8     }
 9
10     toString() {
11         return "(" + this.x + "," + this.y + ")";
12     }
13 }
14
15 let p = new Point(1, 2);
16 // 坐标系点 (1,2)
17 console.log(p.toString()); // (1,2)

上面的代码可以实现我们想要的功能,但是现在又有新需求,坐标系上的点可以为 string 类型的数据,入(一,二),而我们在定义传入 x,y 时候已经定义了它传入的类型必须是 number 类型,如果向函数泛型那样定义 any 类型的话其实就失去了类型校验,跟普通的 js 没有什么区别了,这时候就需要泛型来解决了,如下:

 1 /**
 2  * Point 类的泛型 T
 3  * Point 需要传入什么参数类型
 4  * 就让 T 是什么类型即可
 5  */
 6 class Point<T> {
 7     x: T;
 8     y: T;
 9
10     constructor(x: T, y: T) {
11         this.x = x;
12         this.y = y;
13     }
14
15     toString() {
16         return "(" + this.x + "," + this.y + ")";
17     }
18 }
19
20 let p1 = new Point<number>(1, 2);
21 // 坐标系点 (1,2)
22 console.log(p1.toString()); // (1,2)
23
24 let p2 = new Point<string>("一", "二");
25 // 坐标系点 (一,二)
26 console.log(p1.toString()); // (一,二)

在上面的代码中,我们通过 T 实现了 Point 类型的泛型,Point 需要传入什么类型的参数,我们就将 T 定义成什么类型的数据即可。

3、泛型接口

还是上面的问题,不过这次我们是将 Point 写成接口的形式,如下:

1 interface Point {
2     (x: number, y: number): string
3 }
4
5 let getPoint: Point = (x, y): string => "(" + x + "," + y + ")";
6
7 console.log(getPoint(1, 2)); // (1,2)

在上面的代码中,我们将 x 和 y 定义为 number 类型,这样在传入参数时就只能是 number 类型了,按照上面泛型函数和泛型类的方法,我们将 number 变为 T,如下:

 1 interface Point<T> {
 2     (x: T, y: T): string
 3 }
 4
 5 let getPoint1: Point<number> = (x, y): string => "(" + x + "," + y + ")";
 6 // 坐标系点 (1,2)
 7 console.log(getPoint1(1, 2)); // (1,2)
 8
 9 let getPoint2: Point<string> = (x, y): string => "(" + x + "," + y + ")";
10 // 坐标系点 (一,二)
11 console.log(getPoint2("一", "二")); // (一,二)

通过 T 我们将 Point 接口传入的参数 x,y 进行泛型,这样我们就可以根据自己的需求传入我们想要的参数类型了。

TypeScript 模块

TypeScript 模块的设计理念是可以更换的组织代码。

模块是在其自身的作用域里执行,并不是在全局作用域,这意味着定义在模块里面的变量、函数和类等在模块外部是不可见的,除非明确地使用 export 导出它们。类似地,我们必须通过 import 导入其他模块导出的变量、函数、类等。

两个模块之间的关系是通过在文件级别上使用 import 和 export 建立的。

模块使用模块加载器去导入其它的模块。 在运行时,模块加载器的作用是在执行此模块代码前去查找并执行这个模块的所有依赖。 大家最熟知的JavaScript模块加载器是服务于 Node.js 的 CommonJS 和服务于 Web 应用的 Require.js。

此外还有有 SystemJs 和 Webpack。

模块导出使用关键字 export 关键字,要在另外一个文件使用该模块就需要使用 import 关键字来导入。

接下来我们就来实现一下:

在上图中我们通过 module.ts 中 export 关键字导出我们封装好的属性和方法,在 test.ts 中我们就可以使用 import 关键字来导入我们想要的属性和放大了。

我们也可以通过 export default 来导出模块,如下

但是 export default 在模块导出只能引用一次,导入时也不能使用 { },而是直接导入模块即可。

Typescript 命名空间

在代码量较大的情况下,为了避免各种命名冲突,可将相似功能的函数,类,接口等放置到命名空间内。同 Java 的包,.net 的命名空间一样,Typescript 的命名空间可以将代码包括起来,只对外暴露需要在外部访问的对象,命名空间内的对象通过 export 导出。

举个栗子:假如现在有 A 和 B 两个开发者同时定义了一个 Person 的类,那么这两个类就会引起冲突,为了避免冲突,就不得不再引入其他标识来辨别这两个 Person,空间命名就是为了解决该问题,如下:

 1 /**
 2  * 通过 namespace 来定义命名空间
 3  *分别定义了 A 和 B 两个命名空间
 4  * 我们可以根据需求来调用 A 或 B 里面类和方法
 5  */
 6 namespace A {
 7     interface Person {
 8         name: string,
 9
10         work(): void,
11     }
12
13     export class Student implements Person {
14         name: string;
15
16         constructor(name: string) {
17             this.name = name;
18         }
19
20         work() {
21             console.log(`${this.name}在学习`)
22         }
23     }
24 }
25
26 namespace B {
27     interface Person {
28         name: string,
29
30         work(): void,
31     }
32
33     export class Student implements Person {
34         name: string;
35
36         constructor(name: string) {
37             this.name = name;
38         }
39
40         work() {
41             console.log(`${this.name}在写作业`)
42         }
43     }
44 }
45
46 let a = new A.Student("张三");
47 a.work(); // 张三在学习
48 let b = new B.Student("李四");
49 b.work(); // 李四在写作业

空间命名用起来很简单,只需要通过 namespace 关键字来命名空间即可。

Typescript 装饰器

装饰器是一种特殊类型的生命,它能够被附加到类声明,方法,属性或参数上,可以修改类的行为。通俗的讲装饰器就是一个方法,可以注入到类,方法,属性参数上来扩展类,属性,方法,参数的功能。

常见的装饰器有:类装饰器,属性装饰器,方法装饰器,参数装饰器。

装饰器的写法有:普通装饰器(无法传参),装饰器工厂(可传参)

装饰器是过去几年中 js 最大的成就之一,也是 ES7 的标准特性之一。

1、类装饰器:

 1 /**
 2  * 类小黄使其在声明之前被声明(紧靠着类声明)
 3  * 类装饰器应用于类构造函数
 4  * 可以用来监视,修改或替换类定义
 5  */
 6 function log(params: any) {
 7     console.log(params); // ƒ Person() {}
 8     params.prototype.name = "动态属性";
 9     params.prototype.func = () => {
10         console.log("动态方法");
11     }
12 }
13
14 @log
15 class Person {
16
17 }
18
19 let p: any = new Person();
20 console.log(p.name); // 动态属性
21 p.func(); // 动态方法

在上面的代码中,我们通过 @log 的形式来定义装饰器,在 log 方法中传入一个 params 的参数,通过打印我们发现它就是 Person 方法了,那么我们就可以通过原型链 prototype 的形式添加属性和方法了。

上面@log 并没有传参,接下来我们看一下在调用装饰器的时候传入我们想要的参数:

 1 /**
 2  * 在 log 方法中,我们通过 return 方式
 3  * 将该方法返回,那就跟无参装饰器一样了
 4  * 这样我们就可以在 log 方法中传参了,如 params
 5  */
 6 function log(params: string) {
 7     return function (target: any) {
 8         console.log(params); // 张三
 9         console.log(target); // f Person() {}
10         target.prototype.name = params;
11         target.prototype.work = function (): void {
12             console.log(`${params}在工作`)
13         }
14     }
15 }
16
17 @log("张三")
18 class Person {
19
20 }
21
22 let p: any = new Person();
23 console.log(p.name); // 张三
24 p.work(); // 张三在工作

2、属性装饰器

 1 /**
 2  * 属性装饰器表达式会在运行时当作函数被调用
 3  * 传入下列 2 个参数
 4  * 1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
 5  * 2、成员的名字
 6  */
 7 function log(param: any) {
 8     return function (target: any, key: string) { // 分别传入 2 个参数
 9         console.log(param); // 张三
10         console.log(target); // {constructor: ƒ}
11         console.log(key); // name
12         target[key] = param;
13     }
14 }
15
16
17 class Person {
18     @log("张三")
19     name: string;
20
21     constructor() {
22     }
23
24     getName(): void {
25         console.log(this.name);
26     }
27 }
28
29 let p: any = new Person();
30 console.log(p.name); // 张三
31 p.getName(); // 张三

在上面的代码中,我们在 name:string 上面添加了 @log 的装饰器,这就是属性装饰器,同时我们对装饰器进行了传参“张三”,我们将 log 方法返回,通过打印发现返回的方法的第一个参数 target 为 constructor 构造器,第二个参数为 name,那我们就可以根据 target[key] 的形式对 name 属性进行赋值了。

3、方法装饰器

 1 /**
 2  * 方法装饰器会被应用到方法的属性描述符上
 3  * 用来修改,监视和替换方法定义
 4  * 方法装饰器在运行时传入下列三个参数
 5  * 1、对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
 6  * 2、成员的名字
 7  * 3、成员的属性描述符
 8  */
 9 function log(param: any) {
10     return function (target: any, key: any, description: any) { // 分别传入三个参数
11         console.log(param); // 张三
12         console.log(target); // {getName: ƒ, constructor: ƒ}
13         console.log(key); // getName
14         console.log(description); // {writable: true, enumerable: true, configurable: true, value: ƒ}
15         console.log(description.value); // ƒ () {console.log(this.name) }
16         target.age = 20; // 添加属性
17         target.work = () => console.log(`${param}在工作`); // 添加方法
18         // 修改 getName 方法
19         description.value = ():void => console.log(`${param}---我是被修改的 getName 方法`);
20     }
21 }
22
23
24 class Person {
25     name: string;
26
27     constructor() {
28     }
29
30     @log("张三")
31     getName(): void {
32         console.log(this.name);
33     }
34 }
35
36 let p: any = new Person();
37 console.log(p.age); // 20
38 p.work(); // 张三在工作
39 p.getName(); // 张三---我是被修改的 getName 方法

4、方法参数装饰器

 1 /**
 2  * 方法参数装饰器会在运行时当中函数被调用
 3  * 可以使用参数装饰器为类的原型增加一些元素数据
 4  * 传入下列 3 个参数
 5  * 1、对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
 6  * 2、方法的名字
 7  * 3、参数在函数参数列表中的索引
 8  */
 9 function log(param: any) {
10     return function (target: any, methodName: any, index: any) { // 分别传入三个参数
11         console.log(param); // name
12         console.log(target); // {getData: ƒ, constructor: ƒ}
13         console.log(methodName); // getData
14         console.log(index); // 0
15         target.age = 18;
16         target.work = (): void => console.log(`我在工作`)
17     }
18 }
19
20
21 class Person {
22     constructor() {
23     }
24
25     getData(@log("name") name: string): void {
26         console.log(name);
27     }
28 }
29
30 let p: any = new Person();
31 p.getData("张三"); // 张三
32 console.log(p.age); // 18
33 p.work(); // 我在工作

方法参数装饰器跟其他装饰器不同的是需要将 @log 写在方法的参数内,比较怪,一般情况下方法装饰器就能解决方法参数装饰器所实现的功能,所以一般我们使用方法参数器即可。

装饰器的执行顺序:属性装饰器>方法装饰器>方法参数装饰器>类装饰器,如果有多个相同装饰器,则先执行后面的装饰器。

原文地址:https://www.cnblogs.com/Leo_wl/p/12393883.html

时间: 2024-10-04 13:42:54

TypeScript最大的目的是让程序员更具创造性的相关文章

高效能程序员的修炼阅读

入门 宣扬 "每个人都需要知道如何去编程" (乔布斯说的) 是 一种倒退!举个例子: 马桶堵住了,你不需要特地去学 高级水管工 的课程. 生命中最苦难的是想清楚自己真正要做事情,如果你探索的道路上,决定仍然走上 编程之路,那应该用尽一切方法去学.我的祝福与你同在,当然我的祝福 你听听 就算了,他帮不了你. 绝不要为了学编程而学编程,学编程应该是为了追求快乐 . 我成为程序员是因为我想改变我所玩电脑游戏的规则,而学习编程是唯一的途径. 代码是一种信仰. 善于写作,学会表达,即使没人看 八

[linuxeden] 程序员的野心:让GPU像CPU一样运行

GPU代表的是图形处理单元,但是,这些小小芯片除了处理图形功能,还有其它用处.比如,Google使用GPU来为人脑建模,Salesforce则依赖GPU分析Twitter微博数据流.GPU很适合并行处理运算,也就是同时执行成千上万个任务.怎么做呢?你得开发一个新软件,让它挖掘GPU芯片的潜力.最近美国印第安纳大学计算机博士埃里克-浩克(Eric Holk)就作出尝试,他开发了一个应用程序来运行GPU.浩克说:“GPU编程仍然需要程序员管理许多低层细节,这些细节是与GPU执行的主要任务分离 的.我

程序员,杂草和大树,你选哪个

这篇文章的核心观点是:软件开发者只有打造顶端优势,才能获得更好的发展. 这样的感触,来自: 有位工作十来年的朋友,一直做开发,J2EE.C#.JavaScript.PHP.C++.Android.Python,很多语言.框架和平台都用过,最近他找工作,找来找去,发现每个技术方向上的修炼都很难对得起他的工作年龄,直接导致他找不到比原来工作待遇更好的,非常沮丧. 我建立的IT职业咨询QQ群(522419415)里最近有很多人在找工作,都抱怨说略懂几个方向可找起工作来很难. 那么什么是顶端优势?什么是

36氪上的这七家程序员网站你都了解吗?

我始终相信程序员交付的不是代码,是价值,所以在万众创新下程序员成了最受伤的群体,36氪上的好的项目举不胜举,但是真正为程序员服务的都有哪些呢?让我们一起来看一下. 1,程序员在囧途(http://www.jtthink.com/) 程序员在囧途是中国新兴IT实训教育平台,由<失业的程序员>图书作者沈逸发起,专门为怀才不遇的.迷茫在IT囧途中的程序员所开设在线教育平台. 2,程序员联合开发网(http://www.pudn.com/ ) 作为一个老程序员网站,积累了大量用户和资源,现在是大发展的

一篇写给程序员的提问艺术(转)

作为一个刚入it界的php菜鸟,我感觉自己需要学很多程序员的基本素养,学习如何学习,有效率的学习,精确地学习,热情的学习,加油, 这是一篇关于提问的文章分享给大家吧, (2009年的更新:本文来自2005年的白云黄鹤BBS,未经排版,四年来,文末一直保留有英文原文出处并注明链接) 这个版上太多的问题,不能让我以很愉快的心情来解答,于是,我放弃了强忍着指责别人的心情找到了这篇<提问的艺术>(两年前我在HomePage版张贴过),真诚的希望那些又困难又期望得到帮助的新手朋友们抽时间看看,问&quo

怎样为程序员做职业规划

真正的智慧是拥有对人心的判断力.记得<论语>中也讲过:真正的智慧就是知人,用正直的力量影响周围,影响社会.这种智慧会使我们能够深深地沉静下来,面对每一位程序员以及其背后经历过的历史,能够顺着他心灵上每一条纹路,走进他深处.隐秘.那些欢喜忧伤,那些心灵的愿望,那些对美好生活的设计之路. 经过半年时间的锻炼,大熊和小蔡彼此在技术与素养能力上都提高很快.老板又委任大熊新的重任,承担一个规模更大的项目.由于公司整体管理混乱,其他几个项目组中的程序员辞职现象时有发生,公司整体项目风险加大,经常是拆东墙补

什么样的程序员才算成熟? 让程序员认清自己的所处的阶段

http://www.nowamagic.net/librarys/veda/detail/1450程序员在经历了若干年编程工作之后,很想知道自己水平到底如何?自己是否已经成为成熟的程序员?虽然程序员会对自己有一个自我评价,但是,自己的评价和社会的评价.专业的评价会有差异,所以程序员自己并不能肯定这个评价.现实中,除了各种证书之外,很少有人会专门给出一个程序员的成熟度的评价.人们往往是偶发性地就事论事地对程序员的工作作出好与不好,行与不行的评论.因此,程序员对此感到很茫然,不知道要从那些方面去评

黑马程序员前端培训:高效的前端编程入门训练方法

如今,“前端”这个词已经成为一个大方向的概念,其涵盖的范围可以说非常广:比如浏览器的网页开发.移动App开发.桌面应用开发等等.但是,立足到每一个具体的问题上,前端开发都需要使用到JavaScript这种编程语言.所以,前端学习基础的基础,是要掌握JavaScript这门编程语言. 黑马程序员前端培训,历时多年积累,开设了前端全栈课程.并且通过几千名学员的学习.工作反馈,总结与提炼出以下三点编程入门的训练方法,给想学习前端的初学者一些建议和参考.既然要入门就需要经历一些训练,编程是没有捷径的,可

转职成为TypeScript程序员的参考手册(四)

泛型 对c#程序员来说,TypeScript的泛型很熟悉,基本上是一致的设计. 类型约束 C#使用where关键字标记类型约束,TypeScript在尖括号内使用extends关键字,效果相同. 下面的例子中IExample约束了泛型必须是IMyInterface和他的派生类. 如果像下图这样用的话,就能约束为同时继承ClassOne和ClassTwo的类型.很费解吧,请特别注意. 这是因为本质上TypeScript的类型系统并不那么严格,下面的章节会详细解释TypeScript的类型系统 你也