本篇是我对TS的一些总结,TypeScript虽然和JavaScript语法类似,但他们之间在使用细节上还是有很大的不同的,写本篇目的是便于自己以后查阅和复习。同样也适用于想从JS转为TS的人和一些TS零基础的人。篇幅比较长,建议用PC阅读和查阅。如果本篇博客对你有帮助,感谢你的一个点赞。如果没有,那就 just for me ,就当写给自己看的啦。
虽然简介这一部分可能有人会觉得没必要,但是我觉得还是有必要简单的写一下的。我们以后既然要经常用它,当然得从认识它开始啦!不想看就往下滑咯。
简介:
TypeScript 是 由微软
开发的自由和开源的编程语言。TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准,扩展了 JavaScript 的语法,解决了JS的一些缺点。因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。TypeScript 设计目标是开发大型应用,它可以编译成纯 JavaScript,编译出来的 JavaScript 可以运行在任何浏览器上。也就是说,写是TS写,但最终编译出来还是JS。但要注意TS并不是去替换JS的,它是在JS的基础上构建的。
TypeScript增加的功能: 类型批注和编译时类型检查、类型推断、接口、枚举、Mixin、泛型编程、元组、Await、类、模块、lambda 函数的箭头语法、可选参数以及默认参数等。
学习环境搭建: 1.下载最新版Node.js安装好 2.打开cmd,使用npm安装typescript
npm install -g typescript
TypeScript 在全局安装后,我们可以在任意位置使用 tsc 命令,tsc 命令负责编译 TypeScript 文件为 JavaScript 文件。
TypeScript 使用举例:
方式一是通过全局 tsc 命令编译 TypeScript 代码 创建一个目录:
mkdir ts-practice
cd ts-practice
我们创建一个test.ts文件,里面写一段代码:
export enum TokenType {
ACCESS = 'accessToken',
REFRESH = 'refreshToken'
}
运行tsc命令测试:
tsc test.ts
运行后会得到一个test.js的编译后的文件。
还有一种使用方式是工程化编译方案,涉及的配置和注意点比较多,会在下一篇博客进行详细讲解。
下面开始基础语法的总结,涉及到语法有变量声明、基础类型、对象类型、元组、枚举、接口、类、函数、、泛型、字面量类型、类型断言、类型保护等等。
TypeScript 是 JavaScript 的超集,同 JavaScript 一样,声明变量可以采用var
、let
、const
三个关键字。
TypeScript 变量的命名规则:变量名称可以包含数字和字母。除了下划线 _
和美元 $ 符号
外,不能包含其他特殊字符,包括空格。变量名不能以数字开头。
变量的类型声明是ts的一个非常重要的特点,通过类型声明可以指定当前ts中变量的数据类型。指定类型后,当为变量赋值的时后,TS编译器会自动检查是否符合类型声明,符合则赋值,不符合则报错。简而言之就是类型声明给变量设置了类型,使得变量只能存储某种类型的值。
类型声明的语法:
//1.声明变量的类型,但没有初始值,变量值会设置为 undefined:
let 变量名 : 类型 ;
//2.声明变量的类型及初始值:
let 变量名 : 类型 = 值 ;
//函数参数类型和返回值类型声明
function(参数名1 : 类型 ,参数名2 : 类型):返回值类型{
···
}
JavaScript 的类型分为两种:基础数据类型
和对象类型
(1)布尔值类型
布尔值是最基础的数据类型,在 TypeScript 中,使用 boolean
定义布尔值类型。
let isDone: boolean = false;
注意,使用构造函数 Boolean
创造的对象不是布尔值,编译会报错:
let isMale: boolean = new Boolean(1);
事实上 new Boolean() 返回的是一个 Boolean 对象
,我们要将boolean类型的声明改为Boolean类型声明才不会报错:
let isMale: Boolean = new Boolean(1);
或直接调用 Boolean()
函数 可以返回一个 boolean 类型,这样写也不会报错:
let isMale: boolean = Boolean(1);
(2)数值类型
使用 number
定义数值类型,它可以用来表示整数和分数。
let binaryNumber: number = 0b1010; // 二进制
let octalNumber: number = 0o744; // 八进制
let decNumber: number = 6; // 十进制
let hexNumber: number = 0xf00d; // 十六进制
(3)字符串类型
使用string
定义字符串类型,一个字符系列,使用单引号 ’ 或双引号 " 来表示字符串类型。反引号 ` 来定义多行文本和内嵌表达式。
let myName: string = '害恶细君';
let myAge: number = 20;
// 模板字符串,这其实也是ES6的语法
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`;
编译结果:
let myName = '害恶细君';
let myAge = 20;
// 模板字符串
let sentence = "Hello, my name is " + myName + ".\nI'll be " + (myAge + 1) + " years old next month.";
(4)空值类型
JavaScript 没有空值(Void)的概念,在 TypeScript 中,可以用 void
表示没有任何返回值的函数。可以用于标识方法返回值的类型,表示该方法没有返回值。
function alertName(): void {
alert('My name is haiexijun');
}
声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null。
let unusable: void = undefined;
(5)Null 和 Undefined类型
在 TypeScript 中,可以使用 null
和 undefined
来定义这两个原始数据类型。null表示对象值缺失,undefined
用于初始化变量为一个未定义的值。
let u: undefined = undefined;
let n: null = null;
与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:
// 这样不会报错
let num: number = undefined;
// 这样也不会报错
let u: undefined;
let num: number = u;
而 void 类型的变量不能赋值给 number 类型的变量,下面写编译就会报错:
let u: void;
let num: number = u;
(6)任意值类型
任意值(Any)用来表示允许赋值为任意类型,用any
来表示任意类型,声明为 any 的变量可以赋予任意类型的值。
如果是一个普通类型,在赋值过程中改变类型是不被允许的,编译会报错:
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
但如果是 any 类型,则允许被赋值为任意类型。
let myFavoriteNumber: any = 'seven';
myFavoriteNumber = 7;
声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:
let something;
something = 'seven';
something = 7;
//等价于
let something: any;
something = 'seven';
something = 7;
在 TypeScript 中,数组类型有多种定义方式,比较灵活。
最简单的方法是使用类型 []
来表示数组:
let fibonacci: number[] = [1, 1, 2, 3, 5];
数组的项是不允许出现其他的类型,否则编译会报错:
let fibonacci: number[] = [1, '1', 2, 3, 5];
数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:
let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push('8');
上例中,push 方法只允许传入 number 类型的参数,但是却传了一个 “8” 类型的参数,所以编译也会报错了。
用数组泛型表示数组
我们也可以使用数组泛型Array<elemType>
来表示数组:
let nums: Array<number> = [1, 1, 2, 3, 5];
关于泛型,可以下面会具体讲解。这里主要是想介绍数组泛型可以定义数组。
用接口表示数组 接口也可以用来描述数组(接口后面也会具体讲解):
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
上面的NumberArray 表示:只要索引的类型是数字时,那么值的类型必须是数字。虽然接口也可以用来描述数组,但是我们一般不会这么做,因为这种方式比前两种方式复杂多了,只是稍微了解一下而已。
类数组
类数组(Array-like Object)不是数组类型,比如 arguments
:
function sum() {
let args: number[] = arguments;
}
上例中,arguments
实际上是一个类数组,不能用普通的数组的方式来描述,而应该用接口:
function sum() {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}
在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有 length 和 callee 两个属性。
事实上常用的类数组都有自己的接口定义,如 IArguments
, NodeList
, HTMLCollection
等:
function sum() {
let args: IArguments = arguments;
}
其中 IArguments
是 TypeScript 中定义好了的类型,它实际上就是:
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}
any 在数组中的应用 一个比较常见的做法是,用 any 表示数组中允许出现任意类型:
let list: any[] = ['xcatliu', 25, { website: 'http://www.baidu.com' }];
通过Array对象创建数组
我们也可以使用 Array 对象new Array()
创建数组。Array 对象的构造函数接受以下两种值:表示数组大小的数值。初始化的数组列表,元素使用逗号分隔值。
//指定数组初始化大小:
let arr_names:number[] = new Array(4)
arr_names[1]=12
//或者直接初始化时声明数组元素:
let sites:string[] = new Array("Google","csdn","haiexijun","Facebook")
数组解构 我们也可以把数组元素赋值给变量,如下所示:
let arr:number[] = [12,13]
let [x,y] = arr // 将数组的两个元素赋值给变量 x 和 y
console.log(x)
console.log(y)
数组迭代 我们可以使用 for in语句来循环输出数组的各个元素:
let j:any;
let nums:number[] = [1001,1002,1003,1004]
//迭代打印
for(j in nums) {
console.log(nums[j])
}
多维数组 一个数组的元素可以是另外一个数组,这样就构成了多维数组。最简单的多维数组是二维数组,定义方式如下:
let multiArr:number[][] = [[1,2,3],[23,24,25]]
console.log(multi[0][1])
//输出2
数组在函数中的使用 数组可以作为参数传递给函数:
let sites:string[] = new Array("Google","CSDN","Taobao","haiexijun")
function alertSite(arr_sites:string[]) {
for(let i = 0;i<arr_sites.length;i++) {
console.log(arr_sites[i])
}
}
alertSite(sites);
//输出结果为
Google
CSDN
Taobao
haiexijun
数组还可以作为函数的返回值:
function alertName():string[] {
return new Array("Google", "CSDN", "Taobao", "haiexijun");
}
let sites:string[] = alertName()
for(let i in sites) {
console.log(sites[i])
}
//输出结果为
Google
CSDN
Taobao
haiexijun
常用数组方法总结
方法名 | 作用 |
---|---|
concat() | 连接两个或更多的数组,并返回结果。 |
every() | 检测数值元素的每个元素是否都符合条件。 |
filter() | 检测数值元素,并返回符合条件所有元素的数组。 |
forEach() | 数组每个元素都执行一次回调函数。 |
indexOf() | 搜索数组中的元素,并返回它所在的位置。如果搜索不到,返回值 -1,代表没有此项。 |
join() | 把数组的所有元素放入一个字符串。 |
lastIndexOf() | 返回一个指定的字符串值最后出现的位置,在一个字符串中的指定位置从后向前搜索。 |
map() | 通过指定函数处理数组的每个元素,并返回处理后的数组。 |
pop() | 删除数组的最后一个元素并返回删除的元素。 |
push() | 向数组的末尾添加一个或更多元素,并返回新的长度。 |
reduce() | 将数组元素计算为一个值(从左到右)。 |
reduceRight() | 将数组元素计算为一个值(从右到左)。 |
reverse() | 反转数组的元素顺序。 |
shift() | 删除并返回数组的第一个元素。 |
slice() | 选取数组的的一部分,并返回一个新数组。 |
some() | 检测数组元素中是否有元素符合指定条件。 |
sort() | 对数组的元素进行排序。 |
splice() | 从数组中添加或删除元素。 |
toString() | 把数组转换为字符串,并返回结果。 |
unshift() | 向数组的开头添加一个或更多元素,并返回新的长度。 |
这些数组方法的具体用法不清楚可以再去百度,这里不再代码举例,毕竟那些东西都是js的内容了。
联合类型(Union Types)表示取值可以为多种类型中的一种。语法为:类型1|类型2|类型3
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
myFavoriteNumber = true; //这里报错
这里的 let myFavoriteNumber: string | number 的含义是,允许 myFavoriteNumber 的类型是 string 或者 number,但是不能是其他类型。
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 编译时报错 number类型没有length属性
上例中,第二行的 myFavoriteNumber 被推断成了 string,访问它的 length 属性不会报错。 而第四行的 myFavoriteNumber 被推断成了 number,访问它的 length 属性时就报错了。
Map 对象保存键值对,并且能够记住键的原始插入顺序。任何值(对象或者原始值) 都可以作为一个键或一个值。Map 是 ES6 中引入的一种新的数据结构,可以参考 ES6 Map 与 Set。
创建 Map对象并对其进行操作 TypeScript 使用 Map 类型和 new 关键字来创建 Map:
//创建 Map对象
let myMap = new Map();
// 设置 Map 对象用set(key,value)方法
myMap.set("Google", 1);
myMap.set("CSDN", 2);
myMap.set("Taobao", 3);
// 获取键对应的值用get()方法
console.log(myMap.get(" CSDN")); //2
// 判断 Map 中是否包含键对应的值用has()方法
console.log(myMap.has("Taobao")); // true
console.log(myMap.has("Zhihu")); // false
// 返回 Map 对象键/值对的数量
console.log(nameSiteMapping.size); // 3
// 删除 CSDN
console.log(myMap.delete("CSDN")); // true
console.log(myMap);
// 移除 Map 对象的所有键/值对
myMap.clear();
console.log(myMap);
// 迭代 Map 中的 key
for (let key of myMap.keys()) {
console.log(key);
}
// 迭代 Map 中的 value
for (let value of myMap.values()) {
console.log(value);
}
// 迭代 Map 中的 key => value
for (let entry of myMap.entries()) {
console.log(entry[0], entry[1]);
}
我们知道数组中元素的数据类型都一般是相同的(any[] 类型的数组可以不同),如果存储的元素数据类型不同,则需要使用元组。
元组中允许存储不同类型的元素,元组可以作为参数传递给函数。
用法举例:定义一对值分别为 string 和 number 的元组:
//定义了一个有字符串和数字的的元组,下面是正确写法:
let tom: [string, number] = ['Tom', 25];
console.log(tom[1])
//当当定义了存2个值,却是3个值时就会编译报错,如下:
let tom: [string, number] = ['Tom', 25, 123];
console.log(tom[1])
//内容的类型和定义的顺序不一样,也会编译报错,如下
let tom: [string, number] = [ 25,'Tom'];
console.log(tom[1])
元组运算
我们可以使用以下两个函数向元组添加新元素或者删除元素:
push()
向元组添加元素,添加在最后面。
pop()
从元组中移除元素(最后一个),并返回移除的元素。
解构元组 我们也可以把元组元素赋值给变量,如下所示:
let a =[10,"haiexijun"]
let [b,c] = a
console.log( b )
console.log( c )
接口是一系列抽象方法的声明,是一些方法特征的集合,这些方法都应该是抽象的,需要由具体的类去实现,然后第三方就可以通过这组抽象方法调用,让具体的类执行具体的方法。在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implement)。接口一般首字母大写。有的编程语言中会建议接口的名称加上 I 前缀。
TypeScript 接口定义如下:
interface interface_name {
}
以下实例中,我们定义了一个接口 IPerson,接着定义了一个变量 customer,它的类型是 IPerson。
customer 实现了接口 IPerson 的属性和方法。
interface IPerson {
firstName:string,
lastName:string,
sayHi: ()=>string
}
let customer:IPerson = {
firstName:"zhang",
lastName:"chao",
sayHi: ():string =>{return "Hi there"}
}
console.log("Customer 对象 ")
console.log(customer.firstName)
console.log(customer.lastName)
console.log(customer.sayHi())
联合类型和接口
interface RunOptions {
program:string;
commandline:string[]|string;
}
接口和数组 接口中我们可以将数组的索引值和元素设置为不同类型,索引值可以是数字或字符串。 设置元素为字符串类型,如果使用了其他类型会报错:
interface namelist {
[index:number]:string
}
// 类型一致,正确
let list2:namelist = ["Google","CSDN","Taobao"]
//类型不一致,错误。编译会报错
let list2:namelist = ["haiexijun",1,"Taobao"]
接口继承 接口继承就是说接口可以通过其他接口来扩展自己。Typescript 允许接口继承多个接口。继承使用关键字 extends。
单继承接口例子:
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25
};
多继承举例:
interface IParent1 {
v1:number
}
interface IParent2 {
v2:number
}
interface Child extends IParent1, IParent2 { }
接口的可选属性
interface Person {
name: string;
age?: number;
}
//可以不给age属性赋值
let tom: Person = {
name: 'Tom'
};
接口的任意属性 有时候我们希望一个接口允许有任意的属性,可以使用如下方式:
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
使用 [propName: string] 定义了任意属性取 string 类型的值。 需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集
接口的只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly
定义只读属性:
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
TypeScript 是面向对象的 JavaScript。类描述了所创建的对象共同的属性和方法。TypeScript 支持面向对象的所有特性,比如 类、接口等,
ES6的语法也有类class
的概念。
ES6 中类的用法
属性和方法
使用 class
定义类,使用 constructor
定义构造函数。通过 new 生成新实例的时候,会自动调用构造函数。
class Animal {
public name;
constructor(name) {
this.name = name;
}
sayHi() {
return `My name is ${this.name}`;
}
}
let a = new Animal('害恶细君');
console.log(a.sayHi()); // My name is 害恶细君
类的继承
使用 extends
关键字实现继承,子类中使用 super
关键字来调用父类的构造函数和方法。
class Cat extends Animal {
constructor(name) {
super(name); // 调用父类的 constructor(name)
console.log(this.name);
}
sayHi() {
return 'csdn, ' + super.sayHi(); // 调用父类的 sayHi()
}
}
let c = new Cat('haiexijun'); // haiexijun
console.log(c.sayHi()); // csdn, My name is haiexijun
存取器 使用 getter 和 setter 可以改变属性的赋值和读取行为:
class Animal {
constructor(name) {
this.name = name;
}
get name() {
return 'Jack';
}
set name(value) {
console.log('setter: ' + value);
}
}
let a = new Animal('Kitty'); // setter: Kitty
a.name = 'Tom'; // setter: Tom
console.log(a.name); // Jack
静态方法
使用 static
修饰符修饰的方法称为静态方法,它们不需要实例化,而是直接通过类来调用:
class Animal {
static isAnimal(a) {
return a instanceof Animal;
}
}
静态属性
可以使用 static
定义一个静态属性:
class Animal {
static num = 42;
constructor() {
// ...
}
}
console.log(Animal.num); // 42
TypeScript 中类的用法
TypeScript 可以使用三种访问修饰符,分别是 public
、private
和 protected
。
修饰符 | 作用 |
---|---|
public | 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的 |
private | 修饰的属性或方法是私有的,不能在声明它的类的外部访问 |
protected | 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的 |
class Animal {
public name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name); // Jack
a.name = 'Tom';
console.log(a.name); // Tom
上面的例子中,name 被设置为了 public,所以直接访问实例的 name 属性是允许的。
很多时候,我们希望有的属性是无法直接存取的,这时候就可以用 private 了:
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal('Jack');
console.log(a.name);//编译报错
a.name = 'Tom';//编译报错
使用 private 修饰的属性或方法,在子类中也是不允许访问的,这里就不演示了。如果是用 protected 修饰,则允许在子类中访问。当构造函数修饰为 private 时,该类不允许被继承或者实例化。当构造函数修饰为 protected 时,该类只允许被继承。
类的类型
class Animal {
name: string;
constructor(name: string) {
this.name = name;
}
sayHi(): string {
return `My name is ${this.name}`;
}
}
let a: Animal = new Animal('Jack');
console.log(a.sayHi()); // My name is Jack
用function
来定义函数
function sum(x, y) {
return x + y;
}
函数重载 重载是方法名字相同,而参数不同,返回类型可以相同也可以不同。
每个重载的方法(或者构造函数)都必须有一个独一无二的参数类型列表。
//参数类型不同:
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;
然后,好像也没啥补充点了,其他的大部分也都不常用,就不写进来了。只要记住,JS能用的语法,TS都能用。
如果没有明确的指定类型,那么 TypeScript 会依照类型推论的规则推断出一个类型。
类型推论 以下代码虽然没有指定类型,但是会在编译的时候报错:
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
//事实上等价于
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:
//下面就不会报错
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
类型断言
类型断言可以用来手动指定一个值的类型。
语法是:值 as 类型
或 <类型>值
建议大家在使用类型断言时,统一使用 值 as 类型
这样的语法。因为 <类型>值
这种语法不适用于React。
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中共有的属性或方法:
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
function getName(animal: Cat | Fish) {
//只能访问name
return animal.name;
}
//而有时候,我们确实需要在还不确定类型的时候就访问其中一个类型特有的属性或方法
function isFish(animal: Cat | Fish) {
//获取 animal.swim 的时候会报错。
if (typeof animal.swim === 'function') {
return true;
}
return false;
}
//此时可以使用类型断言,将 animal 断言成 Fish,这样就可以解决访问 animal.swim 时报错的问题了。
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。
枚举使用 enum
关键字来定义:
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"]); // 0
console.log(Days[0]); // Sun
我们也可以给枚举项手动赋值,未手动赋值的枚举项会接着上一个枚举项递增:
enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Sun"]); // 7
console.log(Days["Mon"]); // 1
console.log(Days["Tue"]); // 2
console.log(Days["Sat"]); // 6
当然,手动赋值的枚举项也可以为小数或负数,此时后续未手动赋值的项的递增步长仍为 1:
enum Days {Sun = -7, Mon = 1.5, Tue, Wed, Thu, Fri, Sat};
console.log(Days["Tue"]); // 2
console.log(Days["Mon"]); // 1.5
其他细节就不写了,用到再说。
泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
//首先,我们来实现一个函数 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 允许数组的每一项都为任意类型。但是我们预期的是,数组中每一项都应该是输入的 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;
}
createArray<string>(3, 'x'); // ['x', 'x', 'x']
多个类型参数的泛型:
//定义了一个 swap 函数,用来交换输入的元组。
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
字符串字面量类型用来约束取值只能是某几个字符串中的一个。字符串字面量类型使用 type
进行定义,下面举一个简单的例子:
//使用 type 定了一个字符串字面量类型 EventNames,它只能取三种字符串中的一种。
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
console.log(EventNames)
}
// 没问题
handleEvent('scroll');
// 报错,event 不能为 'focus'
handleEvent('focus');
命名空间是为了避免变量命名冲突,TypeScript 官方将命名空间视为“内部模块”。
如果声明相同名称的命名空间,TypeScript 编译器会将其合并为一个声明。
使用 namespace
关键字来声明命名空间。TypeScript 的命名空间可以将代码包裹起来,只对外暴露这个命名空间对象,通过 export
关键字将命名空间内的变量挂载到命名空间对象上。命名空间本质上就是一个对象,将其内部的变量组织到这个对象的属性上:
namespace Calculator {
const fn = (x: number, y: number) => x * y
export const add = (x: number, y:number) => x + y
}
其编译后的结果:
"use strict";
var Calculator;
(function (Calculator) {
var fn = function (x, y) { return x * y; };
Calculator.add = function (x, y) { return x + y; };
})(Calculator || (Calculator = {}));
那么,我们就可以访问 Calculator 对象上的 add 属性了:
Calculator.add(2, 3)
假如我们想使用第三方库 jQuery,一种常见的方式是在 html 中通过<script> 标签引入 jQuery,然后就可以使用全局变量
声明文件
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。通常我们会把声明语句放到一个单独的文件(如jQuery.d.ts)中,这就是声明文件,声明文件必需以 .d.ts
为后缀。
一般来说,ts 会解析项目中所有的 *.ts 文件,当然也包含以 .d.ts 结尾的文件。所以当我们将 jQuery.d.ts 放到项目中时,其他所有 *.ts 文件就都可以获得 jQuery 的类型定义了。
第三方声明文件 当然,jQuery 的声明文件不需要我们定义了,JQuery已经帮我们定义好了。 我们可以直接下载下来使用,但是更推荐的是使用 @types 统一管理第三方库的声明文件。
@types 的使用方式很简单,直接用 npm 安装对应的声明模块即可,以 jQuery 举例:
npm install @types/jquery --save-dev
如果要搜索你需要的声明文件,可以点击这个链接:https://www.typescriptlang.org查询。
掌握了 TypeScript 的这些语法就像学会了砌墙的工艺,但我们学习 TypeScript 的目的不是为了造一间“小茅屋”,而是为了造“高楼大厦”,这也正是 TypeScript 的类型系统带来的优势。下一篇博客,会具体学习总结TypeScript 工程化的最佳实践方案。