TypeScript学习(进阶篇)

一、元组

数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象。

元组起源于函数编程语言(如 F#),这些语言中会频繁使用元组。

简单的例子

定义一对值分别为 string 和 number 的元组:

let tom: [string, number] = ['Tom', 25];

当赋值或访问一个已知索引的元素时,会得到正确的类型:

let tom: [string, number];
tom[0] = 'Tom';
tom[1] = 25;tom[0].slice(1);
tom[1].toFixed(2);

也可以只赋值其中一项:

let tom: [string, number];
tom[0] = 'Tom';

但是当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。

let tom: [string, number];
tom = ['Tom', 25];
let tom: [string, number];
tom = ['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'.

二、 枚举

枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。

简单的例子

枚举使用 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["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // trueconsole.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true

事实上,上面的例子会被编译为:

var Days;
(function (Days) {Days[Days["Sun"] = 0] = "Sun";Days[Days["Mon"] = 1] = "Mon";Days[Days["Tue"] = 2] = "Tue";Days[Days["Wed"] = 3] = "Wed";Days[Days["Thu"] = 4] = "Thu";Days[Days["Fri"] = 5] = "Fri";Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
手动赋值

我们也可以给枚举项手动赋值:

enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};console.log(Days["Sun"] === 7); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // 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

上面的例子中,递增到 3 的时候与前面的 Sun 的取值重复了,但是 TypeScript 并没有报错,导致 Days[3] 的值先是 "Sun",而后又被 "Wed" 覆盖了。编译的结果是:

var Days;
(function (Days) {Days[Days["Sun"] = 3] = "Sun";Days[Days["Mon"] = 1] = "Mon";Days[Days["Tue"] = 2] = "Tue";Days[Days["Wed"] = 3] = "Wed";Days[Days["Thu"] = 4] = "Thu";Days[Days["Fri"] = 5] = "Fri";Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));

所以使用的时候需要注意,最好不要出现这种覆盖的情况。

手动赋值的枚举项可以不是数字,此时需要使用类型断言来让 tsc 无视类型检查 (编译出的 js 仍然是可用的):

enum Days {Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S"};
var Days;
(function (Days) {Days[Days["Sun"] = 7] = "Sun";Days[Days["Mon"] = 8] = "Mon";Days[Days["Tue"] = 9] = "Tue";Days[Days["Wed"] = 10] = "Wed";Days[Days["Thu"] = 11] = "Thu";Days[Days["Fri"] = 12] = "Fri";Days[Days["Sat"] = "S"] = "Sat";
})(Days || (Days = {}));

当然,手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为 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
console.log(Days["Sat"] === 6.5); // true
常数项和计算所得项

枚举项有两种类型:常数项(constant member)和计算所得项(computed member)。

前面我们所举的例子都是常数项,一个典型的计算所得项的例子:

enum Color {Red, Green, Blue = "blue".length};

上面的例子中,"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.

下面是常数项和计算所得项的完整定义,部分引用自中文手册 - 枚举:

当满足以下条件时,枚举成员被当作是常数:

  • 不具有初始化函数并且之前的枚举成员是常数。在这种情况下,当前枚举成员的值为上一个枚举成员的值加 1。但第一个枚举元素是个例外。如果它没有初始化方法,那么它的初始值为 0
  • 枚举成员使用常数枚举表达式初始化。常数枚举表达式是 TypeScript 表达式的子集,它可以在编译阶段求值。当一个表达式满足下面条件之一时,它就是一个常数枚举表达式:
    • 数字字面量
    • 引用之前定义的常数枚举成员(可以是在不同的枚举类型中定义的)如果这个成员是在同一个枚举类型中定义的,可以使用非限定名来引用
    • 带括号的常数枚举表达式
    • +-~ 一元运算符应用于常数枚举表达式
    • +-*/%<<>>>>>&|^ 二元运算符,常数枚举表达式做为其一个操作对象。若常数枚举表达式求值后为 NaN 或 Infinity,则会在编译阶段报错

所有其它情况的枚举成员被当作是需要计算得出的值。

常数枚举

常数枚举是使用 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 */];

假如包含了计算成员,则会在编译阶段报错:

const enum Color {Red, Green, Blue = "blue".length};// index.ts(1,38): error TS2474: In 'const' enum declarations member initializer must be constant expression.
外部枚举

外部枚举(Ambient Enums)是使用 declare enum 定义的枚举类型:

declare enum Directions {Up,Down,Left,Right
}let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

之前提到过,declare 定义的类型只会用于编译时的检查,编译结果中会被删除。

上例的编译结果是:

var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

外部枚举与声明语句一样,常出现在声明文件中。

同时使用 declare 和 const 也是可以的:

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 */];

三、类和接口

 1. TypeScript中的类和类的实例化

定义类的关键字为 class,后面紧跟类名,类可以包含以下几个模块(类的数据成员): 

字段 : 字段是类里面声明的变量。字段表示对象的有关数据。

构造函数 :类实例化时调用,可以为类的对象分配内存。

方法 : 方法为对象要执行的操作。

class {

        [修饰符] 字段名:[字段类型];

        constructor([参数1:类型,....]){

                // 执行一些初始化操作

        },

       [修饰符] 方法名([参数1:类型,....):返回值类型{

                // 方法体....

        }

}

例如:

class Person{public name:string;private age:number;constructor(name:string,age:number){this.name = name;this.age = age;}public setAge(age:number){this.age = age;}public getAge():number{return this.age;}
}let person = new Person("xiaoming",20);
console.log(person.name);
console.log(person.getAge());

2. 类的继承,以及类继承后super的使用

class User{private name:string;constructor(name:string){this.name = name;}public setName(name:string):void{this.name = name;}public getName():string{return this.name;}
}class Admin extends User{private password:string;constructor(name:string,password:string){super(name);this.password = password;}public setPassword(password:string){this.password = password;}public getPassword():string{return this.password;}
}let user1 = new User("xujingliang");
console.log(user1.getName());let admin = new Admin("xujingliang","123456")
console.log(admin.getName());
console.log(admin.getPassword());

3. 类继承后的方法重写

类继承后,子类可以对父类的方法重新定义,这个过程称之为方法的重写。

重写是子类继承父类之后,方法名称,参数名称,返回值类型全部相同只能修改方法体中的相关操作。

重写的优点:在拥有父类方法特征的同时,自身也可以有一些特有的特征

class User{private name:string;constructor(name:string){this.name = name;}public setName(name:string):void{this.name = name;}public getName():string{return this.name;}public say():string{return "My name is "+this.name;}
}class Admin extends User{private password:string;constructor(name:string,password:string){super(name);this.password = password;}public setPassword(password:string){this.password = password;}public getPassword():string{return this.password;}public say():string{return "My name is "+super.getName()+" and my password is "+this.password;}
}let user = new User("xujingliang");
console.log(user.say());let admin = new Admin("xujingliang","123456")
console.log(admin.say());

上面的代码示例中,我们先定义了一个User用户类,又定义了一个Admin管理员类。Admin类继承User类,并对User类的say()方法进行重写。 

4. static关键字的使用

使用static生命的类字段或者方法不需要实例化对象,可以直接通过类名点字段名称或者类名点方法名称直接调用。

class Person{public name:string;private age:number;static mark:string = "普通用户"constructor(name:string,age:number){this.name = name;this.age = age;}public setAge(age:number){this.age = age;}public getAge():number{return this.age;}
}
console.log(Person.mark);

示例代码中,声明了一个static字段mark,直接使用Person.mark就可以访问。 

5. instanceof 运算符

instanceof 运算符用于判断对象是否是指定的类型,如果是返回 true,否则返回 false。

class User{private name:string;constructor(name:string){this.name = name;}public setName(name:string):void{this.name = name;}public getName():string{return this.name;}
}class Admin extends User{private password:string;constructor(name:string,password:string){super(name);this.password = password;}public setPassword(password:string){this.password = password;}public getPassword():string{return this.password;}
}let user1 = new User("xujingliang");
let admin = new Admin("xujingliang","123456")console.log(user1 instanceof User);
console.log(user1 instanceof Admin);
console.log(admin instanceof Admin);
console.log(admin instanceof User);

注意:示例代码中,Admin类继承User类,因此用Admin示例化的对象既属于User类有属于Admin类

6. 访问控制修饰符

类成员可访问性定义了类的成员允许在何处被访问。和Java一样TypeScript也为类成员提供了三种可访问性修饰符。

1. public:类的公有成员没有访问限制,可以在当前类的内部、外部以及派生类的内部访问。类的公有成员使用public修饰符标识。在默认情况下,类的所有成员都是公有成员。

2. protected:类的受保护成员允许在当前类的内部和派生类的内部访问,但是不允许在当前类的外部访问。类的受保护成员使用protected修饰符标识。

3. private:类的私有成员只允许在当前类的内部被访问,在当前类的外部以及派生类的内部都不允许访问。类的私有成员使用private修饰符标识。

4. readonly:使用 readonly 关键词设置成员只读,初始化过后,readonly 不管在外部还是内部都不允许再修改。

class User{private username:string;private password:string;private readonly state:boolean = false;constructor(username:string,password:string){this.username = username;this.password = password;}public setUsername(username:string):void{this.username = username;}public getUsername():string{return this.username;}public setPassword(password:string):void{this.password = password;}public getPassword():string{return this.password;}public setState(state:boolean):void{// 此处会报错:Cannot assign to 'state' because it is a read-only property.(2540)this.state = state;}
}

7. 类的封装

面向对象的三大特性:封装、继承、多态。封装是指将类的某些信息隐藏在类的内部,不允许外部程序直接访问,而是通过该类提供的方法来对隐藏的信息进行操作和访问。我们在程序设计的过程中要追求“高内聚,低耦合”。高内聚就是类的内部数据操作细节自己来完成,不允许外部干涉,低耦合:就是, 仅暴露少量的方法给外部使用

结合访问修饰符private即可实现类的封装

class User{private username:string;private password:string;constructor(username:string,password:string){this.username = username;this.password = password;}public setUsername(username:string):void{this.username = username;}public getUsername():string{return this.username;}public setPassword(password:string):void{this.password = password;}public getPassword():string{return this.password;}
}

上述代码示例中,用户只能通过暴露的setUsername、getUsername、setPassword、getPassword等方法来访问和操作User的username和password属性。

8. 类和接口

类与类之间的一些共同点可以用接口去抽象,比如 人 和 动物 都有相同的特点,吃和行走,我们就可以通过接口去约束这两个类的公共能力。接口使用关键字 interface 声明,类可以使用关键字 implements 实现接口。

interface Person{name:string
}class User implements Person{name:string;constructor(name:string){this.name = name;}public setName(name:string):void{this.name = name;}public getName():string{return this.name;}
}class Admin extends User{private password:string;constructor(name:string,password:string){super(name);this.password = password;}public setPassword(password:string){this.password = password;}public getPassword():string{return this.password;}
}let user1 = new User("xujingliang");
let admin = new Admin("xujingliang","123456")

9. 抽象类和抽象方法

抽象类在某种程度上跟接口有点类似,它也是用来约束子类当中必须要拥有某些成员,但是不同于接口的是,抽象类可以包含一些具体的实现,而接口只能够是一些成员的抽象,不包含具体的实现,一些比较大的类目建议使用抽象类,比如动物类,因为我们所说的动物它只是一个泛指,并不够具体,那在它的下面一定有更细化的分类,例如小狗小猫之类。

定义抽象类的方式:通过关键词 abstract

1. 类型被定义为抽象类过后,它只能够被继承,不能够再使用 new 的方式去创建对应的实例对象,在这种情况下我们就必须要使用 子类 去继承这个类型。

2. 在抽象类当中我们还可以去定义一些抽象方法,需要注意的是抽象方法也不需要方法体。

3. 当我们的父类有抽象方法时,我们的子类就必须要实现这个方法。

// 抽象类
abstract class Animal{// 非抽象方法cry(message:string):void{console.log(message)}// 抽象方法 抽象方法不能有具体的实现abstract say():void
}class Dog extends Animal{// 如果继承的抽象类中有抽象方法,则子类必须去实现抽象方法,否则编译检查报错public say():void{console.log("I am Dog");}}

 四、泛型

1. 使用泛型创建数组

let users:Array<string> = ['xujingliang','lanyangyang','xiyangyang']console.log(users);

首先,我们来实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值:

function createArray(length: number, value: any): Array<any> {let result = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;
}createArray(3, 'x'); // ['x', 'x', 'x']

上例中,我们使用了之前提到过的数组泛型来定义返回值的类型。

这段代码编译不会报错,但是一个显而易见的缺陷是,它并没有准确的定义返回值的类型:

Array<any> 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 value 的类型。

这时候,泛型就派上用场了:

function createArray<T>(length:number,value:T):Array<T>{let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;
}let arr:any = createArray<string>(10,"x");console.log(arr);

2. 泛型接口

interface Person<T>{name:T;
}class User implements Person<string>{name:string;constructor(name:string){this.name = name;}
}

3. 泛型类

示例:

class User<T>{data:T;constructor(data:T){this.data = data;}
}let user1:User<string> = new User<string>("helloworld");
console.log(user1.data);let user2:User<number> = new User<number>(20);
console.log(user2.data);

 在Java开发中接口返回的结果常常这样封装

// 后端返回数据接口封装class ResultUtil<T>{code:number;message:string;data:T;constructor(code:number,message:string,data:T){this.code = code;this.message = message;this.data = data;}
}// 当T是字符串时
let res1:ResultUtil<string> = new ResultUtil<string>(200,"操作成功","添加成功");
console.log(JSON.stringify(res1))
// 输出结果:"{"code":200,"message":"操作成功","data":"添加成功"}" // 当T是数组时
let res2:ResultUtil<number[]> = new ResultUtil<number[]>(200,"操作成功",[20,30,40,60]);
console.log(JSON.stringify(res2))
// 输出结果:"{"code":200,"message":"操作成功","data":[20,30,40,60]}"

4. 泛型参数的默认类型

我们可以为泛型中的类型参数指定默认类型。当使用泛型时没有在代码中直接指定类型参数,从实际值参数中也无法推测出时,这个默认类型就会起作用。

function createArray<T = string>(length: number, value: T): Array<T> {let result: T[] = [];for (let i = 0; i < length; i++) {result[i] = value;}return result;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/299650.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

网络通信协议

WebSocket通信 WebSocket是一种基于TCP的网络通信协议&#xff0c;提供了浏览器和服务器之间的全双工通信&#xff08;full-duplex&#xff09;能力。在WebSocket API中&#xff0c;浏览器和服务器只需要完成一次握手&#xff0c;两者之间就直接可以创建持久性的连接&#xff…

一个int型有序数组,如何拿到最低的值和这个值一共存在多少个?

一个int型有序数组&#xff0c;如何拿到最低的值和这个值一共存在多少个&#xff1f; 示例代码&#xff1a; int min 0;int os 1;int[] element {5,4,4,2,2};for (int i : element) {if (i min) {os;} else {min i;os 1;}}System.out.println("min " min);S…

《Git快速入门》Git分支

1.master、origin、origin/master 区别 首先搞懂git分支的一些名称区别&#xff1a; master &#xff1a; Git 的默认分支名字。它并不是一个特殊分支、跟其它分支完全没有区别。 之所以几乎每一个仓库都有 master 分支&#xff0c;是因为 git init 命令默认创建它&#xff0c…

Python爬虫中的多线程、线程池

进程和线程的基本介绍 进程是一个资源单位&#xff0c;线程是一个执行单位&#xff0c;CPU调度线程来执行程序代码。 当运行一个程序时&#xff0c;会给这个程序分配一个内存空间&#xff0c;存放变量等各种信息资源&#xff0c;而这个内存空间可以说是一个进程&#xff0c; 一…

自学SLAM(9)《第五讲:特征点法视觉里程计》作业

文章目录 1.ORB特征点1.1 ORB提取1.2 ORB描述1.3 暴力匹配1.4 最后&#xff0c;请结合实验&#xff0c;回答下⾯⼏个问题 2.从 E 恢复 R&#xff0c;t3.用 G-N 实现 Bundle Adjustment4.* 用 ICP 实现轨迹对齐 1.ORB特征点 1.1 ORB提取 ORB(Oriented FAST and BRIEF) 特征是 S…

Android camera打开摄像头、预览

一、activity_main.xml代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:app"http://schemas.a…

【MySQL】数据库之事务

目录 一、什么是事务 二、事务的ACID是什么&#xff1f; 三、有哪些典型的不一致性问题&#xff1f; 第一种&#xff1a;脏读 第二种&#xff1a;不可重复读 第三种&#xff1a;幻读 第四种&#xff1a;丢失更新 四、隔离级别有哪些&#xff1f; &#xff08;1&#xf…

doris基本操作,05-Rollup

简述 Rollup类似于mysql的视图&#xff0c;区别在于视图并没有将数据独立存储&#xff0c;视图是逻辑上的连接。而Rollup将数据独立存储了&#xff0c;玩的是真的。当查询命中Rollup时&#xff0c;会从Rollup表里获取数据&#xff0c;提高查询效率。 操作 创建Rollup表 alt…

codeforces round 894题解 A~F

文章目录 A. Gift Carpet题目大意思路AC代码 B. Sequence Game题目大意思路AC代码 C. Flower City Fence题目大意思路AC代码 D. Ice Cream Balls题目大意思路AC代码 E. Kolya and Movie Theatre题目大意思路AC代码 F. Magic Will Save the World题目大意思路AC代码 A. Gift Car…

【软件工程】可执行文件和数据分离

一、概述 可执行文件和数据分离是一种软件设计策略&#xff0c;旨在将程序代码和程序使用的数据分离存储。这种方法通常用于提高软件的模块化程度和灵活性&#xff0c;以及方便软件的管理和维护。 在可执行文件和数据分离中&#xff0c;程序代码通常以可执行文件的形式存储&a…

搭建Nginx文件下载站点

一、下载Nginx 首先&#xff0c;确保你的服务器上已经安装了Nginx&#xff0c;使用编译安装&#xff0c;下载最新版Nginx。 wget https://nginx.org/download/nginx-1.25.3.tar.gz tar -xf nginx-1.25.3.tar.gz二、安装Fancyindex和Nginx-Fancyindex-Theme模块 # 下载Fancyin…

MyBatis:Generator

MyBatis Generator附批量操作分页查询存储过程 Generator 介绍网址&#xff1a;Introduction to MyBatis Generator Generator &#xff0c;一个用于 MyBatis 的代码生成工具&#xff0c;可以根据数据库表结构自动生成对应的实体类、DAO 接口和 SQL 映射文件&#xff0c;提高…