typeScript
安装方式
npm install -g typescript
检测版本号
cmd/ tsc -v
优缺点
优点
- 增加了代码的可读性和可维护性
- 类型系统实际上是最好的文档,大部分函数看看类型定义就知道如何使用了
- 可以在编译阶段就发现大部分错误,这总比在运行的时候出错好
- 增强了编译器和IDE,包括代码补全,接口提示,跳转定义,代码重构
- 非常包容
- 文件后缀为
.ts - 及时不显示的定义类型,也能够自动做出类型推论
- 编译报错的时候也会生成
js文件 - 兼容第三方库,及时第三方空不是用
ts写的,也可以编写单独的类型文件提供ts读取
- 文件后缀为
- 活跃的社区
- 大把的三方库都有提供给
ts的类型定义文件 angular、vue、vscode、Ant Design等耳熟能祥的项目就是用的ts- 可以使用
ES6
- 大把的三方库都有提供给
缺点
-
有一定的学习成本,需要理解新的内容
- 接口(
Interfaces) - 泛型(
Generics) - 类(
Classes) - 枚举类型(
Enums)等前端工程师可能不是很熟悉的概念
- 接口(
-
短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,
TypeScript能够减少其维护成本 -
成到构建流程需要一些工作量
基础
原始数据类型
JavaScript 的类型分为两种:原始数据类型和对象类型(Object types)。
原始数据类型包括:布尔值、数值、字符串、null、undefined 以及 ES6 中的新类型 Symbol 和 BigInt。
boolean/Boolean
布尔值是最基础的数据类型,在TypeScript中,使用boolean定义布尔值类型
let isTool: boolean = false;
// 编译通过// 后面约定,未强调编译错误的代码片段,默认为编译通过
注意,使用构造函数Boolean创造的对象不是布尔值
let isTool2: boolean = new Boolean(1);
Type 'Boolean' is not assignable to type 'boolean'.
'boolean' is a primitive, but 'Boolean' is a wrapper object. Prefer using 'boolean' when possible.
事实上new Boolean()返回的是一个boolean对象:
let tool3: Boolean = new Boolean(1);
直接调用Boolean也可以返回一个boolean类型:
let tool4: boolean = Boolean(1);
在 TypeScript 中,boolean 是 JavaScript 中的基本类型,而 Boolean 是 JavaScript 中的构造函数。其他基本类型(除了 null 和 undefined)一样,不再赘述。
Number
使用 number 定义数值类型:
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d;
// ES6 中的二进制表示法
let binaryLiteral: number = 0b1010;
// ES6 中的八进制表示法
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
编译结果:
var decLiteral = 6;
var hexLiteral = 0xf00d;
// ES6 中的二进制表示法
var binaryLiteral = 10;
// ES6 中的八进制表示法
var octalLiteral = 484;
var notANumber = NaN;
var infinityNumber = Infinity;
其中 0b1010 和 0o744 是 ES6 中的二进制和八进制表示法,它们会被编译为十进制数字。
string
使用 string 定义字符串类型:
let myName: string = 'Tom';
let myAge: number = 25;
// 模板字符串
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`;
编译结果:
var myName = 'Tom';
var myAge = 25;
// 模板字符串
var sentence = "Hello, my name is " + myName + ".\nI'll be " + (myAge + 1) + " years old next month.";
空值
javascript没有空值Void的概念,在TypeScript中,可以用void表示没有任何返回值的函数:
function alertName(): void {
alert('My name is Tom');
}
声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:
let unusable: void = undefined;
编译结果:
var unusable = undefined;
Null 和 Undefined
在 TypeScript 中,可以使用 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;
编译结果:
var num = undefined;
// 这样也不会报错
var u;
var num2 = u;
而 void 类型的变量不能赋值给 number 类型的变量:
let u: void;
let num: number = u;
这样会直接报错,编译结果为
var u;
var num = u;
任意值
任意值(Any)用来表示允许赋值为任意类型。
什么是任意值类型
如果是一个普通类型,在赋值过程中改变类型是不被允许的:
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
但如果是 any 类型,则允许被赋值为任意类型。
let myFavoriteNumber: any = 'seven';
任意值的属性和方法
在任意值上访问任何属性都是允许的:
let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);
也允许调用任何方法:
let anyThing: any = 'tom';
anyThing.setName('jerry');
anyThing.setName('jerry').sayHello();
anyThing.myName.setFirstName('cat');
可以认为,声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值。
未声明类型的变量
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型:
let something;
something = 'seven';
something = 7;
something.setName('Tom');
等价于
let something: any;
something = 'seven';
something = 7;
something.setName('Tom');
类型推论
如果没有明确的指定类型,那么TypeScript会依照类型推论的规则推断出一个类型。
什么是类型推论
let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
事实上,它等价于:
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:
let myFavoriteNumber;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种。
简单的例子
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
let myFavoriteNumber: string | number;
myFavoriteNumber = true;
联合类型使用|分隔每个类型。
这里的let myFavoriteNumber: string | number;的含义是,允许myFavoriteNumber的类型是string或者number,但是不能是其他类型。
访问联合类型的属性或方法
当TypeScript不确定一个联合类型的变量到底是哪个类型的时候,我们__只能访问此联合类型的所有类型里共有的属性或方法:__
function getLength(something: string | number): number {
return something.length;
}
// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
上例中,length不是string和number的共有属性,所以会报错。
访问string和number的共有属性是没问题的:
function getString(something: string | number): string {
return something.toString();
}
联合类型的变量在被赋值的时候,会根据类型推论的规则推断出一个类型:
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 编译时报错
// index.ts(5,30): error TS2339: Property 'length' does not exist on type 'number'.
上例中,第二行myFavoriteNumber被推断成了string,访问它的length属性不会报错。而第四行myFavoriteNumber被推断成了number,访问length属性就会报错了。
对象的类型——>接口
在TypeScript中,我们使用接口来定义对象的类型。
什么是接口
在面对对象语言中,接口是一个很重要的概念,它是对行为的抽象,而具体如何行动需要类(class)去实现(implement)。
TypeScript中的接口是一个非常灵活的概念,出了可用于对类的一部分行行为进行抽象以外,也常用于[对象的形状(shaoe)]进行描述。
简单的例子
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'tom',
age: 25
}
上面的例子中,我们定义了一个接口person,接着定义了一个变量tom,它的类型是Person。
这样, 我们就约束了tom的形状必须和接口person一致。
接口一般首字母大写。有点变成语言中中会建议接口的名称加上I前缀。
定义的变量比接口少了一些属性是不允许的:
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom'
};
// index.ts(6,5): error TS2322: Type '{ name: string; }' is not assignable to type 'Person'.
// Property 'age' is missing in type '{ name: string; }'.
多一些属性也是不允许的:
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: 'Tom',
age: 25,
gender: 'male'
};
// index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
可见,赋值的时候,变量的形状必须和接口的形状保持一致。
可选属性
有时候我们希望不要完全匹配一个形状,那么可以用可选属性:
interface Person {
name: string;
age?: number;
}
let tom: Person = {name:'tom'}
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'tom',age: 25
}
可选属性的含义是该属性可以不存在。
这时__仍然不允许添加未定义的属性:__
interface Person {
name: string;
age?: number;
}
let tom: Person = {
name: 'tom',age: 25, gender: 'male'
}
// examples/playground/index.ts(9,5): error TS2322: Type '{ name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Object literal may only specify known properties, and 'gender' does not exist in type 'Person'.
任意属性
有适合我们希望一个接口允许有任意的属性,可以使用如下方式:
interface Person {
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'tom', gender: 'male'
}
使用[propName: string]定义了任意属性取string类型的值。
需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集:
interface Person {
name: string;
age?: number;
[propName: string]: string;
}
let tom: Person = {name: 'Tom',age: 25,gender: 'male'};
// index.ts(3,5): error TS2411: Property 'age' of type 'number' is not assignable to string index type 'string'.
// index.ts(7,5): error TS2322: Type '{ [x: string]: string | number; name: string; age: number; gender: string; }' is not assignable to type 'Person'.
// Index signatures are incompatible.
// Type 'string | number' is not assignable to type 'string'.
// Type 'number' is not assignable to type 'string'.
上例中,任意属性的值允许是string,但是可选属性age的值确实number,number不是string的子属性,所以报错了。
另外,在报错信息中可以看出,此时{name: 'tom',age: 25,gender: 'male'}的类型被推断成了{[x: string]: string | number; name: string; age: number; gender: string;},这是联合类型和接口的结合。
一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:
interface Person {
name: string;
age?: number;
[propName: string]: string | number;
}
let tom: Person = {
name: 'tom', gender: 'male'
}
只读属性
有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用readonly定义只读属性:
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
id:4864,name: 'tom', gender: 'male'
}
tom.id = 4864;
// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
上例中,使用readonly定义的属性id初始化后,又被赋值了,所以报错了。
主要,只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候:
interface Person {
readonly id: number;
name: string;
age?: number;
[propName: string]: any;
}
let tom: Person = {
name: 'tom', gender: 'male'
}
tom.id = 4864;
// index.ts(8,5): error TS2322: Type '{ name: string; gender: string; }' is not assignable to type 'Person'.
// Property 'id' is missing in type '{ name: string; gender: string; }'.
// index.ts(13,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.
上例中,报错信息有两处,第一处是在对tom进行赋值的时候,没有给id赋值。
第二处是在给tom.id赋值的时候,有雨它是只读属性,所以报错了。
数组的类型
在TypeScript中,数组类型有多种定义方式,比较灵活。
【类型 + 方括号】表示法
最简单的方式的使用[类型 + 方括号]来表示数组:
let fibonacci: number[] = [1,1,2,3,5];
数组的项中__不允许__出现其他的类型:
let fibonacci: number[] = [1,'1',2,3,5];
// Type 'string' is not assignable to type 'number'.
数组的一些方法的参数也会根据数组在定义时约定的类型进行限制:
let fibonacci: number[] = [1, 1, 2, 3, 5];
fibonacci.push('8');
// Argument of type '"8"' is not assignable to parameter of type 'number'.
上例中,push方法只允许传入number类型的参数,但是却传了一个"8"类型的参数,所以报错了。这里"8"是一个字符串字面量类型
数组泛型
也可以使用数组泛型Array<elemType>来表示数组:
let fibonacci: Array<number> = [1,1,2,3,5];
用接口表示数组
接口也可以用来描述数组:
interface NumberArray {
[index: number]: number;
}
let fibonacco: NumberArray = [1, 1, 2, 3, 5];
NumberArray表示:只要索引的类型是数字时,那么值的类型必须是数字。
虽然接口也可以用来描述数组,但是我们一般不会这么做,因为这种方式比前两种方式复杂多了。
不过有一种情况例外,那就是他常用来表示类数组。
类数组
类数组不是数组类型,比如arguments:
function sum(){
let args: number[] = arguments;
}
// Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more.
上例中,arguments实际上是一个类数组,不能用普通的数组的方式来描述,而应该用接口:
function sum(){
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}
在这个例子中,我们除了约束当索引的类型是数字时,值的类型必须是数字之外,也约束了它还有length和callee两个属性。
事实上常用的类数组都有自己的接口定义,如,IArguments,NodeList,HTMLCollection等:
let args: IArguments = arguments;
其中IArguments是TypeScript中定义好了的类型,它实际上就是:
interface IArguments {
[index: number]: any;
length: number;
callee:Function;
}
any在数组中的应用
一个比较常见的做法是,用any表示数组中允许出现任意类型:
let list:any[] = ['lcy',25,{website:'http://www.baidu.com'}];