1、简介
TypeScript 是 JavaScript 的一个超集,主要提供了类型系统和对 ES6 的支持。
TypeScript 的命令行工具安装方法入下:
npm install -g typescript
安装完就可以使用 tsc 命令,来编译 TypeScript 文件,生成 .js 文件。
tsc hello.ts
注意:
TypeScript 只会进行静态检查,如果发现有错误,编译的时候就会报错。但是 TypeScript 编译的时候即使报错了,还是会生成编译结果。
2、基础
TypeScript 中使用 :
指定变量的类型,:
的前后有没有空格都可以。(不指定变量类型不会报错。)
2.1、原始数据类型
(1)布尔值
布尔值是最基础的数据类型,在 TypeScript 中,使用 boolean 定义布尔值类型。
let isDone: boolean = false;
注意: 使用构造函数 Boolean 创造的对象不是布尔值。
let creatByNewBoolean: boolean = new Boolean();
/*
报错:error TS2322: Type 'Boolean' is not assignable to
type 'boolean'. 'boolean' is a primitive, but 'Boolean'
is a wrapper object. Prefer using 'boolean' when possible.
*/
let creatByNewBoolean: Boolean = new Boolean();
// 正确
(2)数值
使用 number 定义数值类型:
let num: number = 6;
(3)字符串
使用 string 定义字符串类型:
let str: string = 'string';
let tem: string = `Hello, ${str}`;
(4)空值
JavaScript 中没有空值(void)的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数。
function alerName(): void{}
声明一个 void 类型的变量无意义,因为它只能被赋值为 undefined 和 null。
(5)Null 和 undefined
在 TypeScript 中,可以使用 null 和 undefined 来定义这两个原始类型的数据。
let u: undefined = undefined;
let n: null = null;
undefined 类型的变量只能被赋值为 undefined, null 类型的变量只能被赋值为 null。
注意:
与 void 不同,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给其他类型的变量。
let num1: number = undefined;
let num2: number = null;
// 不会报错。
let num3: number = void;
// 报错
2.2、任意值
如果是一个普通类型,在赋值过程中不允许改变类型,会报错。但是 any 类型,则允许被赋值为任意类型。
let anyType: any = 'seven';
anyType = 7;
在任意值上访问任何属性和方法都是允许的。(但是在执行编译后的 js 文件会报错)
let anyThing: any = 'hello';
console.log(anyThing.name);
anyThing.setName('XX");
变量如果在声明的时候,未指定类型,那么它会被识别为任意值类型。
// 不会报错
let someThing;
someThing = 1;
someThing = 'one';
// 不同于以下代码,以下代码会报错
let s = 1;
s = 'one';
2.3、类型推论
如果没有明确指定类型,那么 TypeScript 会依照类型推论的规则推断出一个类型。
let myDate = 'seven';
=> 等价于 let myDate: string = 'seven';
在 TypeScript 2.1 之前,如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型,而且完全不被检查类型。
在 TypeScript 2.1 中,变量类型为最后一次赋值类型。
2.4、联合类型
联合类型表示取值可以为多种类型中的一种。联合类型使用 | 分隔每个类型。
let myDate: string | number;
当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,只能访问此联合类型的所有类型里面共有的属性或方法。
let mydate: string | number;
mydate.length; // 报错 Length 不是 string 和 number 的共有属性。
联合类型的变量在被赋值的时候,会根据类型推断的规则推断出一个类型,此时使用该类型的方法不会报错。
mydate = 'seven';
console.log(mydate.length); // 5
mydate = 7;
console.log(mydate.length); // 报错。number 没有Length属性
2.5、对象的类型——接口
在 TypeScript 中,使用接口来定义对象的类型,并且定义的变量必须和接口要保持一致。
interface Person{
name: string;
age: number;
}
let xl: Person = { // 必须和接口保持一致
name: 'xl';
age: 25;
}
let error1: Person = { // 报错,比接口少了一个属性
name: 'e1';
}
let error2: Person = { // 报错,比接口多了一个属性
name: 'e2';
age: 23;
website: 'ss'
}
可以使用 ? 定义可选属性。可选属性可以在变量中不实现,但是在变量中不能添加新的属性。
interface Person{
name: string;
age?: number;
}
可以使用 [propName: type] 定义任意属性。
interface Person{
name: string;
age: number;
[propName: string]: string;
}
注意:一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性。
let x: Person = {
name: 'x';
age: 25;
website: 'ss';
}
任意属性的值允许是 string,但是 age 却是 number,number 不是 string 的子属性,所以报错了。
只读属性是通过 readonly 定义。该值只能在创建的时候被赋值。
interface Person{
readonly id: number;
}
只读属性,初始化后,在被赋值就会报错。并且只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候。
2.6、数组的类型
在 TypeScript 中,数组类型的定义:
1、类型 + 方括号 表示法
let arr: number[] = [1,2,3];
2、数组泛型 Array<elemType> 来表示
let arr: Array<number> = [1,2,3];
3、用接口表示数组
interface NumberArray{
[index: number]: number;
}
let arr: NumberArray = [1,2,3];
4、any
let list: any[] = ['x',2,{}];
5、类数组
function sum() {
let args: number[] = arguments;
}
2.7、函数的类型
(1)一个函数的输入和输出,要在 TypeScript 中对其进行约束。
function sum(x: number, y: number): number{}
let sum = function(x:number, y:number): number{} // 通过类型推论来判断 sum 的类型
let sum: (x: number, y: number) => number =
function(x:number, y:number): number{}
注意: 此时,调用的时候参数要与声明的一致。
sum(1); // 报错
sum(1,2,3); // 报错
(2)接口中函数的定义
interface Func{
(source: string, subString: sting): boolean;
}
let func: Func;
func = function(source: string, subString: sting){
return true;
}
(3)使用 ? 表示可选的参数。可选参数后面不允许在出现必须参数。
function build(f: string, l?: string){}
function build(f?: string, l: string){} // 报错
(4)参数默认值
function build(f: string, l: string = 's'){}
(5)剩余参数
function build(array, ...items){}
(6)允许函数重载
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string{}
2.8、类型断言
类型断言可以用来绕过编译器的类型推断,手动指定一个值的类型。
语法
1、<类型>值
2、值 as 类型
类型断言不是类型转换,断言成一个联合类型中不存在的类型是不允许的。
function getLength(some: string | number): number{
// <boolean>some 报错
if((<string>some).length){
return (<string>some).length;
}else{
return some.toString().length;
}
}
2.9、声明文件
当使用第三方库时,我们需要引用它的声明文件。使用 declare 关键字来定义类型,帮助 TypeScript 判断传入的参数类型对不对。该定义只用于编译时检查,编译结果中会被删除。
declare var jQuery: (string) => any;
jQuery('#foo');
通常使用单独的文件声明,然后引入。
// jQuery.d.ts
declare var jQuery: (string) => any;
用 三斜线指令(///)表示引用了声明文件
/// <reference path="./jQuery.d.ts"
jQuery('#foo');
2.10、内置对象
TypeScript 包含了 JavaScript 的内置对象,但是不包含 nodejs 的内置对象,使用 nodejs 的内置对象时需要引入第三方声明文件:
npm install @types/node --save-dev
3、进阶
3.1、类型别名
可以使用 type 为类型起个别名。
type Name = string;
type NameResolver = () => string;
3.2、字符串字面量类型
字符串字面量类型用来约束取值只能是某几个字符串中的一个。
type EventNames = 'click' | 'scroll' | 'mousemove'
EventNames = 'dbclick' // 报错
3.3、元组
数组合并了相同类型的对象,元组合并了不同类型的对象。
let s: [string, number] = ['s', 22];
let x: [string, numner];
x[0] = 'xx';
x[1] = ss;
x[0].sclice(1);
或只赋值其中一项。
但当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。
x = [‘ss’]; // 报错
let a: [string, number] = [‘ss’] // 报错
当赋值给越界的元素时,它类型会被限制为元组中每个类型的联合类型。
x = ['s', 22, 'aaa'];
数组第三项满足联合类型 string | number。
3.4、枚举
枚举使用 enum 关键字来定义,枚举成员会被赋值从 0 开始递增的数组。同时也会对枚举值到枚举名进行反向映射:
enum Days {Sun, Mon};
Days['Sun'] === 0; // true
Days[0] === 'Sun'; // true
也可以使用手动赋值。此时,未手动赋值的枚举项会接着上一个枚举项递增。需要注意的是,未手动赋值的枚举项与手动赋值重复时, TypeScript 不会报错,但是会覆盖之前的定义:
enum Days {Sun = 3,Mon = 1, Tue, Wed}
Days['Sun'] === 3; // true
Days['Mon'] === 1; // true
Days['Tue'] === 2; // true
Days['Web'] === 3; // true
Days[3] === 'Sun'; // false
Days[3] === 'Web'; // true
手动赋值的枚举可以不是数字,此时需要使用类型断言来让 tsc 无视类型检查(编译出的 js 仍然是可用的):
enum Days {Sun = <any>"s"}
手动赋值的枚举也可以为小数或负数,此后未手动赋值的项的递增步长仍为 1。
常数枚举是使用 const enum 定义的枚举类型,它会在编译阶段被删除,并且不能包含计算成员:
const enum Dir{up, down};
let dir = [Dir.up, Dir.down];
编译结果:
var dir = [0, 1];
外部枚举是使用 declare enum 来定义的枚举类型:
declare enum Dir{up, down}
let dir = [Dir.up, Dir.down];
编译结果
var dir = [Dir.up, Dir.down];
3.5、类
TypeScript 除了实现了所有 ES6 中的类的功能外,还添加了一些新的用法。
TypeScript 中类的用法。
(1)public private 和 protected
TypeScript 可以使用三种访问修饰符,分别是 public private 和 protected。
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有属性都是 public 的。
- private 修饰的属性或方法是私有的,不能再声明它的类的外部访问。
- protected 修饰的属性或方法是受保护的,它和 private 类型,区别是它在子类中也是允许被访问的。
(2)抽象类
abstract 用于定义抽象类和其中的抽象方法。抽象类是不允许被实例化的。并且,抽象类中的抽象方法,必须被子类实现。
abstract class Animal{
public name;
public constrictor(name){
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal{
public sayHi(){
console.log(`${this.name}`);
}
}
let cat = new Cat('Tom);
(3)类的类型
给类加上 TypeScript 的类型很简单,与接口类似:
class Person{
name: string;
constructor(name: string){
this.name = name;
}
sayHi(): string{
return `${this.name}`
}
}
let a: Person = new Person('Jack');
a.sayHi(); => Jack
3.6、类与接口
- 类至多继承(extends)一个类,和实现(implements)多个接口。
- 接口与接口之间可以是继承关系。
- 接口可以继承类
混合类型:一个函数可以有自己属性和方法。
interface Couter{
(start: number): number;
interval: number;
reset(): void;
}
function getCounter(): Counter{
let counter = function(start: number){};
counter.interval = 123;
counter.reset = function(){}
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5;
3.7、泛型
(1)泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,而是在使用的时候再指定类型的一种特性。
function createArray<T>(length: number, value: T): Array<t>{}
createArray<string>(3, 'x');
或 createArray(3, 'x');
(2)多个类型的参数。
function swap<T, U>(typle: [T, U]): [U, T]{}
swap([1, 's']);
(3)泛型约束,在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意操作它的属性或方法。这时可以对泛型进行约束。
interface Lengthwise{
length: number;
}
function logging<T extends Lengthwise>(arg: T): T{
arg.length;
}
调用的时候,传入的参数如果不包含 Length 将会报错。
(4)多个类型参数之间可以相互约束。
(5)可以使用含有泛型的接口来定义一个函数需要符合的形状。
interface Func{
<T>(length: number, value: T): Array<T>;
}
let fun: Func;
=> 等价于
interface Func<T>{
(length: number, value: T): Array<T>;
}
let fun: Func<any>;
fun = function<T>(length: number, value: T): Array<T>{}
fun(3, 'x');
3.8、声明合并
(1)函数的合并
我们可以使用重载定义多个函数类型:
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
(2)接口的合并
接口中的属性在合并时会简单的合并到一个接口中:
interface Alarm {
price: number;
}
interface Alarm {
weight: number;
}
相当于:
interface Alarm {
price: number;
weight: number;
}
注意:合并的属性的类型必须是唯一的。