TypeScript(TS)是JavaScript的超集,其可以编译出纯净、 简洁的JavaScript代码,并且可以运行在任何浏览器上、Node.js环境中和任何支持ECMAScript 3(或更高版本)的JavaScript引擎中。TypeScript提供最新的和不断发展的JavaScript特性,包括那些来自2015年的ECMAScript和未来的提案中的特性,比如异步功能和Decorators,以帮助建立健壮的组件。
我们这次只是对TS进行简单的介绍与学习,关于TS更多详细的内容可以从以下网站获取信息。
TS中文官网:
https://www.tslang.cn/index.html
其他相关网站
https://typescript.p6p.net/
https://vue3js.cn/interview/typescript/typescript_javascript.html
https://space.bilibili.com/482867012
1、TS与JS的区别
TS是JS的超集,所以现有的JS程序都可以在TS下工作,并且TS还需要被编译为JS才能被浏览器识别进行工作,它们的关系如下图所示。
两者之间存在一些区别,主要是(1)后缀文件不同,TS文件后缀为.ts;(2)JS是弱类型语言,在语法上JS是动态类型语言,而TS是强类型语言,在语法上是静态类型语言。这里需要对弱类型/强类型、动态类型/静态类型进行解释。
类型(type)指的是一组具有相同特征的值,比如数值类型、字符串类型,在运算过程中只能数值类型和数值类型进行求和运算,而不能和字符串类型求和。但是JS语言的类型系统比较弱,使用起来也没限制,比如一个变量x
定义时,定义为数值123
,但是在后面也可以直接修改其值为"abc"
,这样就直接将数值类型改为了字符串类型,所以JS是动态类型。而TS中使用了更严格的类型系统,当变量X
被定义时需要添加类型声明,被预先声明为数值型123
后,就不能赋值为"abc"
,变量的类型是静态的。TS的使用,为JS引入了静态类型特征。
静态类型有一定的优点和缺点。优点包括:有利于代码的静态分析和发现错误;更好的IDE支持,便于代码补全和语法提示;有利于提高代码质量,保证代码安全,适合在大型项目中使用。缺点包括:丧失了动态类型的代码灵活性,增加了编程的工作量和学习成本,过去的一些JS项目可能也会存在兼容性问题,所以TS不一定适合那些小型、短期项目。
此外,TS可以直接在官方网页进行简单的练习:https://www.tslang.cn/play/index.html
2、类型声明与类型断言
在TS中定义变量时,可以进行声明变量类型后,赋值再使用。
let x: number = 123;
console.log(x);
但是类型声明并不是必须的,所有TS会自己推断类型,如下代码所示,并没有声明变量类型,但是如果将变量更改为其他类型,TS也会报错。
let x = 123;
x = "hello";
//会报错
类型断言是提示编译器如何处理这个变量,类型断言有两种方式,目前推荐使用第二种。
type T = "a" | "b" | "c";
let foo = "a";
// <类型>值
<Type>value;
let bar: T = <T>foo;
// 值 as 类型
value as Type;
let bar: T = foo as T;
这里定义T时使用了type
命令和联合类型
,type
用于定义类型别名,这里为string
类型定义了别名T
,所以能使用T
作为类型,需要注意的是别名不允许重复,并且别名的作用域是块级作用域,代码块内部定义的别名,影响不到外部。此外,别名允许嵌套。
3、类型系统
TS继承了JS的一些类型,所以TS的基本类型包括:string
、number
、object
、null
、boolean
、bigint
、symbol
、undefined
。其中,null
和undefined
既是值,又是类型。这里需要注意的是都是小写字母,大写的Number
、String
等在JS中属于内置的对象。
在TS中还有联合类型union types,指的是多个类型组合成一个新的类型,使用符号|
。如下例子所示,将联合类型和定义类型别名一起使用,只要一个类型属于string或者number,那么就是属于联合类型T。
type T = number | string;
let x1: T = 123;
let x2: T = "abc";
console.log(x1);
console.log(x2);
// 也可以不使用类型别名
let x3: string | number;
x3 = 123;
console.log(x3);
x3 = "abc";
console.log(x3);
4、数组、元组、枚举
(1)数组 array
TS数组与JS数组最大的不同也是需要保证成员的类型一致,但是成员的数量也是不确定的。这里数组的类型也可以使用联合类型。
// 两种方式,约束数组为数值类型
let arr: number[] = [1,2,3];
let arr: Array<number> = [1, 2, 3];
//联合类型,第一种方式必须使用括号
let arr: (number|string)[];
let arr:Array<number|string>;
// 不设置数组成员类型,但是需要避免使用
let arr:any[];
示例:
let arr: (number | string)[];
arr = [1, 2, 3];
arr[3] = "abc";
console.log(arr);
(2)元组 tuple
元组(tuple)是TS新增的数据类型,JS没有这种类型,它表示成员类型可以自由设置的数组,即数组的各个成员的类型可以不同;元组成员的类型可以添加问号后缀?
,表示该成员是可选的,但是问号只能用在元组尾部的成员身上,所有可选成员必须在必选成员之后。
// 元组,元组结构的声明;可以使用问号设置为某个值为不必须输入
let t1: [number, string, number?]=[1,"a"]
(3)枚举 Enum
枚举也是TS新增数据类型,但是它也是一个值,其将相关的常量等放在一起便于使用和调用。调用enum的成员可以使用点运算符或者方括号。
// 定义一个枚举结构
enum Color {
red,
blue,
green,
}
//等同于如下JS结构
var Color;
(function (Color) {
Color[Color["red"] = 0] = "red";
Color[Color["blue"] = 1] = "blue";
Color[Color["green"] = 2] = "green";
})(Color || (Color = {}));
// 调用
let c1 = Color.blue;
let c2 = Color["red"];
console.log(c1);
console.log(c2);
在上面的例子中,声明了一个 Enum 结构Color
,里面包含三个成员red
、blue
和green
。第一个成员的值默认为整数0
,第二个为1
,第二个为2
,以此类推。而调用 后,c1和c2并不是具体的颜色,而是1和0。
5、函数
TS中的函数,需要在声明函数时,给出参数的类型和返回值的类型;但是返回值的类型可以不写,因为TS自己会推断出来。
// 这里没有返回值,使用了void
function hello(txt: string): void {
console.log("hello " + txt);
}
// 可以返回值类型缺省
function hello(txt: string) {
console.log("hello " + txt);
}
(1)箭头函数
在JS学习阶段,我们其实也了解了箭头函数的部分内容。箭头函数是普通函数的一种简化的写法。
在JS中箭头函数的语法如下所示:
//常规:
let myFunction = function(a, b){
return a * b;
}
// 箭头函数:
let myFunction = (a, b) => {
return a * b;
}
// 进一步省略
let myFunction = (a, b) => a * b;
在TS中也类似,只是添加了类型声明。类型声明需要写在定义里面,其中参数类型写在参数名后面,返回值的类型写在参数列表后面。示例如下所示:
let myFunction = (a: number, b: number): number => a * b;
var c = myFunction(3, 4);
console.log(c);
// 控制台显示结果12
(2)函数重载
有些函数可以接受不同类型或不同个数的参数,并且根据参数的不同,会有不同的函数行为;这种根据参数类型不同,执行不同逻辑的行为,称为函数重载(function overload)。在C++等语言中均有使用,在TS中的详细信息参考:http://t.csdnimg.cn/fO4sT与TypeScript 的函数类型 | 阮一峰 TypeScript 教程 (p6p.net)。
有人用这样一个比喻来说明函数重载:吃饭是一个函数,表示一个吃饭功能,但西方人用叉子,中国人用筷子,这就是细节不同,我们可以用函数重载来解决。
关于函数重载需要首先了解函数签名,函数签名=函数名称+函数参数+函数参数类型+返回值类型。函数重载包括了许多的规则,我们就不深入的学习,下面是一个函数重载的例子:
function reverse(str: string): string;
function reverse(arr: any[]): any[];
function reverse(stringOrArray: string | any[]): string | any[] {
if (typeof stringOrArray === "string")
return stringOrArray.split("").reverse().join("");
else return stringOrArray.slice().reverse();
}function hello(txt: string): void {
console.log("hello " + txt);
}
在示例中,函数reverse()
可以将参数颠倒输出,参数可以是字符串,也可以是数组;前两行先对函数的两种参数情况进行了类型的说明,第三行是函数本身的类型说明,第三行必须与前面两行重载声明兼容,这样保证了输入字符串返回颠倒后的字符串,输入数组而输出的是颠倒后的数组。
(3)构造函数
在JS中构造函数constructor
是一种用于创建和初始化类对象实例的特殊方法,使用new
运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。在TS中,构造函数的类型写法,就是在参数列表前面加上new
命令,构造函数可以传递类也可以是对象,在下面的实例中,类型 F 就是一个构造函数,类型写成一个可执行对象的形式,并且在参数列表前面要加上new
命令。
// 构造函数采用对象形式
type F = {
new (s: string): object;
};
某些函数既是构造函数,又可以当作普通函数使用,比如Date()
函数。类型声明可以写成如下格式:
type F = {
new (s: string): object;
(n?: number): number;
};
6、接口
接口interface是对象的模板,用来指定对象的类型结构。任何实现接口的对象,都必须满足接口的所有属性。
interface Student{
name: string;
Id: number;
grade: string;
}
// 接口实现,Student起到类型的作用
const a: Student = {
name: "张三",
Id:654321,
grade:"良好",
};
console.log(a);
接口继承
接口的继承使用extends
关键字,接口可以继承接口、类class和类型type,继承后的接口就有了新的属性。以下为例,接口rectangle继承了backgroundColor和Perimeter的属性,有了5个属性。
interface backgroundColor {
color: string;
}
interface perimeter {
width: number;
height: number;
peri: number;
}
interface rectangle extends backgroundColor, perimeter{
area: number;
}
7、类
TS中的类可以设置属性的类型声明,如果不设置会默认类型为any
,如果设置了初始值也会根据初始值的类型为属性推断类型。
类的修饰符、存取器等具体的用法可以参考网站。
https://typescript.bootcss.com/classes.html
https://typescript.p6p.net/typescript-tutorial/class.html
类的装饰器:
https://typescript.p6p.net/typescript-tutorial/decorator.html
https://typescript.net.cn/docs/handbook/decorators.html
class Point {
x: number;
y: number;
}
(1)修饰符
TS中修饰符有很多类型,包括公共修饰符public、私有修饰符private、受保护修饰符protected、只读修饰符readonly、静态修饰符static、抽象修饰符abstract。具体每个修饰符的用法可以参考网站。
https://typescript.bootcss.com/classes.html
https://typescript.p6p.net/typescript-tutorial/class.html
// readonly修饰符
class Shp {
readonly Fid = "0";
}
(2)存取器
TypeScript支持通过getters/setters来截取对对象成员的访问,帮助你有效的控制对对象成员的访问,详细用法可以参考上面的网址。
存取器包括包括取值(get
)和存值(set
),其规则包括:(1)如果某个属性只有get方法,没有set方法,那么该属性判定为只读;(2)set方法的参数类型,必须兼容get方法的返回值类型;(3)get方法与set方法的可访问性必须一致,要么都为公开方法,要么都为私有方法。
class C {
_name = "";
get name() {
return this._name;
}
set name(value) {
this._name = value;
}
}
(3)类的 interface 接口
类实现接口使用implements
关键字,为当前类添加当前接口中的限制条件。类可以实现多个接口,如果类和接口同名,接口也会合并到类的定义中。如下所示,定义了phone接口,那么Xiaomi就也应该满足phone接口中的条件。
interface phone{
CPU:string;
RAM:number;
}
class Xiaomi implements phone{
CPU = "骁龙8";
RAM = 16;
}
console.log(Xiaomi);
(4)装饰器
装饰器 是一种特殊的声明,可以附加到 类声明、方法、访问器、属性 或 参数。装饰器使用 @expression
的形式,其中 expression
必须计算为一个函数,该函数将在运行时使用有关装饰声明的信息进行调用。
8、泛型
泛型被用来创建可重用的组件,一个组件可以支持多种类型的数据, 这样用户就可以以自己的数据类型来使用组件。泛型的特点就是带有 类型参数 (type parameter)。
示例中,函数getFirst()
的函数名后面尖括号的部分<T>
,就是类型参数,参数要放在一对尖括号(<>
)里面。本例只有一个类型参数T
,可以将其理解为类型声明需要的变量,需要在调用时传入具体的参数类型。getFirst()
的参数类型是T[]
,返回值类型是T
,就清楚地表示了两者之间的关系。比如,输入的参数类型是number[]
,那么 T 的值就是number
,因此返回值类型也是number
。
function getFirst<T>(arr: T[]): T {
return arr[0];
}
// 调用函数
console.log(getFirst<number>([1, 2, 3]));
泛型类
泛型类的类型参数写在类名后面。
class Pair<K, V> {
key: K;
value: V;
}
// 调用
let Pair1 = new Pair<number, number>();
Pair1.key = 1;
Pair1.value = 2;
console.log(Pair1);
除了泛型类外,函数、接口、类型别名都可以使用泛型。泛型的详细介绍与使用可以参考网址:泛型(generic) - TypeScript 中文手册 (bootcss.com),TypeScript 泛型 | 阮一峰 TypeScript 教程 (p6p.net)。
Pair1 = new Pair<number, number>();
Pair1.key = 1;
Pair1.value = 2;
console.log(Pair1);
[外链图片转存中...(img-EnUj45VX-1724516962200)]
除了泛型类外,函数、接口、类型别名都可以使用泛型。泛型的详细介绍与使用可以参考网址:[泛型(generic) - TypeScript 中文手册 (bootcss.com)](https://typescript.bootcss.com/generics.html),[TypeScript 泛型 | 阮一峰 TypeScript 教程 (p6p.net)](https://typescript.p6p.net/typescript-tutorial/generics.html)。