需要注意的是,这篇文章是王二本人在刷了一遍TypeScript
的语法后,认为有些地方需要着重注意,于是在这里做的一个小总结。 > 如果需要系统的过一遍TypeScript
的语法,这里重点推荐微软大神xcatliu的TypeScript 辅导教程,王二就是看的这篇教程写的这篇文章,也可以看TypeScript 文档(中文)系统了解
一、什么是 TypeScript
TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。TypeScript 是开源的。
二、TypeScript 的特点
王二认为 TypeScript 最大的特点是 可以进行静态检查语法,可以在编译阶段就发现大部分错误,这一点和 java 很像。
而且 TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可。
三、安装 TypeScript
TypeScript 的命令行工具安装方法如下:
npm install -g typescript
以上命令会在全局环境下安装 tsc 命令,安装完成之后,我们就可以在任何地方执行 tsc 命令了。
我们约定使用 TypeScript 编写的文件以 .ts 为后缀,
编译一个 TypeScript 文件很简单:
tsc hello.ts
然后就会在 hello.ts 同一级的目录下生成一个 hello.js 文件。
四、推荐编辑器
当然是推荐Visual Studio Code啦,它本身就是由 TypeScript
编写的,而且天然支持对 TypeScript
支持。
五、一个简单的例子
将以下代码复制到 hello.ts 中:
1
2
3
4
5
6
|
function sayHello(person: string) {
return "Hello, " + person;
}
let user = "Tom";
console.log(sayHello(user));
|
然后执行
tsc hello.ts
这时候会生成一个编译好的文件 hello.js:
1
2
3
4
5
|
function sayHello(person) {
return "Hello, " + person;
}
var user = "Tom";
console.log(sayHello(user));
|
TypeScript 中,使用 : 指定变量的类型,: 的前后有没有空格都可以。
上述例子中,我们用 : 指定 person 参数类型为 string。但是编译为 js 之后,并没有什么检查的代码被插入进来。
如果发现有错误,编译的时候就会报错。
下面尝试把这段代码编译一下:
1
2
3
4
5
6
|
function sayHello(person: string) {
return "Hello, " + person;
}
let user = [0, 1, 2];
console.log(sayHello(user));
|
编辑器中会提示错误,编译的时候也会出错:
index.ts(6,22): error TS2345: Argument of type ’number[]’ is not assignable to parameter of type ‘string’.
但是还是生成了 js 文件:
1
2
3
4
5
|
function sayHello(person) {
return "Hello, " + person;
}
var user = [0, 1, 2];
console.log(sayHello(user));
|
TypeScript 编译的时候即使报错了,还是会生成编译结果,我们仍然可以使用这个编译之后的文件。
六、空值
JavaScript 没有空值(Void)的概念,在 TypeScirpt 中,可以用 void 表示没有任何返回值的函数:
1
2
3
|
function alertName(): void {
alert("My name is Tom");
}
|
声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null:
1
|
let unusable: void = undefined;
|
七、Null 和 Undefined
在 TypeScript 中,可以使用 null 和 undefined 来定义这两个原始数据类型:
1
2
|
let u: undefined = undefined;
let n: null = null;
|
undefined 类型的变量只能被赋值为 undefined,null 类型的变量只能被赋值为 null。
与 void 的区别是,undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给 number 类型的变量:
1
2
3
4
5
|
// 这样不会报错
let num: number = undefined;
// 这样也不会报错
let u: undefined;
let num: number = u;
|
而 void 类型的变量不能赋值给 number 类型的变量:
1
2
3
4
|
let u: void;
let num: number = u;
// index.ts(2,5): error TS2322: Type 'void' is not assignable to type 'number'.
|
八、任意值
任意值(Any)用来表示允许赋值为任意类型,参考如下代码:
1
2
3
4
|
let myFavoriteNumber: string = "seven";
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
|
但如果是 any 类型,则允许被赋值为任意类型。
1
2
|
let myFavoriteNumber: any = "seven";
myFavoriteNumber = 7;
|
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
九、类型推论
如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。
以下代码虽然没有指定类型,但是会在编译的时候报错:
1
2
3
4
|
let myFavoriteNumber = "seven";
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
|
事实上,它等价于:
1
2
3
4
|
let myFavoriteNumber: string = "seven";
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
|
TypeScript 会在没有明确的指定类型的时候推测出一个类型,这就是类型推论。
##如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查:##
1
2
3
|
let myFavoriteNumber;
myFavoriteNumber = "seven";
myFavoriteNumber = 7;
|
十、联合类型
联合类型(Union Types)表示取值可以为多种类型中的一种。
例如上面的代码:
1
2
3
4
|
let myFavoriteNumber = "seven";
myFavoriteNumber = 7;
// index.ts(2,1): error TS2322: Type 'number' is not assignable to type 'string'.
|
用联合类型写就不会报错:
1
2
|
let myFavoriteNumber: string | number = "seven";
myFavoriteNumber = 7;
|
十一、接口
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implements)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述,参考如下代码:
1
2
3
4
5
6
7
8
9
|
interface Person {
name: string;
age: number;
}
let tom: Person = {
name: "Tom",
age: 25,
};
|
上面的例子中,我们定义了一个接口 Person,接着定义了一个变量 tom,它的类型是 Person。这样,我们就约束了 tom 的形状必须和接口 Person 一致。
但是这个时候变量少了或者多了都会报错,我们可以用可选属性、任意属性、只读属性来进一步制定接口,详细可以了解这篇文章
十二、定义数组类型
在 TypeScript 中,数组类型有多种定义方式,比较灵活。
以下三种方式都可以定义数组:
1
2
3
4
5
6
7
8
9
|
//最简单的方法是使用「类型 + 方括号」来表示数组:
let fibonacci1: number[] = [1, 1, 2, 3, 5];
//也可以使用数组泛型(Array Generic) Array<elemType> 来表示数组:
let fibonacci2: Array<number> = [1, 1, 2, 3, 5];
//接口也可以用来描述数组:
interface NumberArray {
[index: number]: number;
}
let fibonacci3: NumberArray = [1, 1, 2, 3, 5];
|
以上的代码中,数组中类型要求一致,如果想允许出现任意类型,可以用 any :
1
|
let list: any[] = ["Wanger", 22, { website: "http://www.wangyulue.com" }];
|
Typescript 还实现了常见的类数组的接口定义,如 IArguments, NodeList, HTMLCollection 等,例如接受函数内的 arguments 对象:
1
2
3
|
function sum() {
let args: IArguments = arguments;
}
|
十三、约束函数的类型
在 Typescript 中也可以约束函数的输入和输出,参考如下代码:
1
2
3
|
function sum(x: number, y: number): number {
return x + y;
}
|
这时候 sum 函数被约束为只接受两个 number 类型并输出为一个 number 类型的函数。
现在哪怕输入多余的(或者少于要求的)参数,都是不被允许的:
1
2
3
4
5
6
7
8
9
10
11
12
|
function sum(x: number, y: number): number {
return x + y;
}
sum(1, 2, 3);
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
function sum(x: number, y: number): number {
return x + y;
}
sum(1);
// index.ts(4,1): error TS2346: Supplied parameters do not match any signature of call target.
|
我们也可以使用接口的方式来定义一个函数需要符合的形状:
1
2
3
4
5
6
7
8
|
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function (source: string, subString: string) {
return source.search(subString) !== -1;
};
|
这时候其实还有 可选参数、参数默认值、剩余参数、重载的概念,有兴趣可以了解这篇文章
十四、申明文件
假如我们想使用第三方库,比如 jQuery,我们通常这样获取一个 id 是 foo 的元素:
1
2
3
|
$("#foo");
// or
jQuery("#foo");
|
但是在 TypeScript 中,我们并不知道 $ 或 jQuery 是什么东西:
1
2
3
|
jQuery("#foo");
// index.ts(1,1): error TS2304: Cannot find name 'jQuery'.
|
这时,我们需要使用 declare 关键字来定义它的类型,帮助 TypeScript 判断我们传入的参数类型:
1
2
3
|
declare var jQuery: (string) => any;
jQuery("#foo");
|
declare 定义的类型只会用于编译时的检查,编译结果中会被删除。
上例的编译结果是:
通常我们会把类型声明抽出来放到一个单独的文件中,这就是声明文件:
1
2
3
|
// jQuery.d.ts
declare var jQuery: (string) => any;
|
我们约定声明文件以 .d.ts 为后缀。
然后在使用到的文件的开头,用「三斜线指令」表示引用了声明文件:
1
2
3
|
/// <reference path="./jQuery.d.ts" />
jQuery("#foo");
|
当然,jQuery 的声明文件不需要我们定义了,已经有人帮我们定义好了:jQuery in DefinitelyTyped。
我们可以直接下载下来使用,但是更推荐的是使用工具统一管理第三方库的声明文件。
社区已经有多种方式引入声明文件,不过 TypeScript 2.0 推荐使用 @types 来管理。
@types 的使用方式很简单,直接用 npm 安装对应的声明模块即可,以 jQuery 举例:
npm install @types/jquery –save-dev
可以在这个页面搜索你需要的声明文件。
十五、内置对象
ECMAScript
标准提供的内置对象有:
Boolean
、Error
、Date
、RegExp
等。
我们可以在 TypeScript 中将变量定义为这些类型:
1
2
3
4
|
let b: Boolean = new Boolean(1);
let e: Error = new Error("Error occurred");
let d: Date = new Date();
let r: RegExp = /[a-z]/;
|
更多的内置对象,可以查看 MDN 的文档。
而他们的定义文件,则在 TypeScript 核心库的定义文件中。
十六、类型别名
一个简单的例子:
1
2
3
4
5
6
7
8
9
10
|
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;
function getName(n: NameOrResolver): Name {
if (typeof n === "string") {
return n;
} else {
return n();
}
}
|
上例中,我们使用 type
创建类型别名。
类型别名常用于联合类型。
十七、字符串字面量类型
字符串字面量类型用来约束取值只能是某几个字符串中的一个,举一个简单的例子:
1
2
3
4
5
6
7
8
9
|
type EventNames = "click" | "scroll" | "mousemove";
function handleEvent(ele: Element, event: EventNames) {
// do something
}
handleEvent(document.getElementById("hello"), "scroll"); // 没问题
handleEvent(document.getElementById("world"), "dbclick"); // 报错,event 不能为 'dbclick'
// index.ts(7,47): error TS2345: Argument of type '"dbclick"' is not assignable to parameter of type 'EventNames'.
|
上例中,我们使用 type
定了一个字符串字面量类型 EventNames
,它只能取三种字符串中的一种。
注意,类型别名与字符串字面量类型都是使用 type 进行定义。
十八、类
我们先来回顾一下 ES6 中类的用法,这里当然推荐阮一峰大神的ES6-class 教程,或者王二总结的ES6 中类的使用简明教程
TypeScript
有三种访问修饰符(Access Modifiers),分别是 public
、private
和 protected
:
public
修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public
的
private
修饰的属性或方法是私有的,不能在声明它的类的外部访问
protected
修饰的属性或方法是受保护的,它和 private
类似,区别是它在子类中也是允许被访问的
这里和 Java 的访问修饰符很像,Java 还多一个default
例如,有时候我们希望有的属性是无法直接存取的,这时候就可以用 private 了:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
class Animal {
private name;
public constructor(name) {
this.name = name;
}
}
let a = new Animal("Jack");
console.log(a.name); // Jack
a.name = "Tom";
// index.ts(9,13): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
// index.ts(10,1): error TS2341: Property 'name' is private and only accessible within class 'Animal'.
|
需要注意的是,TypeScript
编译之后的代码中,并没有限制 private
属性在外部的可访问性。
而且使用 private
修饰的属性或方法,在子类中也是不允许访问的;而如果是用 protected
修饰,则允许在子类中访问。
给类加上 TypeScript 的类型很简单,与接口类似:
1
2
3
4
5
6
7
8
9
10
11
12
|
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
|
十九、类与接口
接口(Interfaces)可以不但可以用于对「对象的形状(Shape)」进行描述,而且可以对类的一部分行为进行抽象。
实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提高了面向对象的灵活性。
举例来说,门是一个类,防盗门是门的子类。如果防盗门有一个报警器的功能,我们可以简单的给防盗门添加一个报警方法。这时候如果有另一个类,车,也有报警器的功能,就可以考虑把报警器提取出来,作为一个接口,防盗门和车都去实现它:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
interface Alarm {
alert();
}
class Door {}
class SecurityDoor extends Door implements Alarm {
alert() {
console.log("SecurityDoor alert");
}
}
class Car implements Alarm {
alert() {
console.log("Car alert");
}
}
|
一个类可以实现多个接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
interface Alarm {
alert();
}
interface Light {
lightOn();
lightOff();
}
class Car implements Alarm, Light {
alert() {
console.log("Car alert");
}
lightOn() {
console.log("Car light on");
}
lightOff() {
console.log("Car light off");
}
}
|
上例中,Car 实现了 Alarm 和 Light 接口,既能报警,也能开关车灯。
二十、混合类型
之前学习过,可以使用接口的方式来定义一个函数需要符合的形状:
1
2
3
4
5
6
7
8
|
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function (source: string, subString: string) {
return source.search(subString) !== -1;
};
|
有时候,一个函数还可以有自己的属性和方法,这时候可以使用混合类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
function getCounter(): Counter {
let counter = <Counter>function (start: number) {};
counter.interval = 123;
counter.reset = function () {};
return counter;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
|
二十一、泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。
例如,我们来实现一个函数 createArray,它可以创建一个指定长度的数组,同时将每一项都填充一个默认值:
1
2
3
4
5
6
7
8
9
|
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
的类型。
这时候,泛型就派上用场了:
1
2
3
4
5
6
7
8
9
|
function createArray<T>(length: number, value: T): Array<T> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
createArray<string>(3, "x"); // ['x', 'x', 'x']
|
上例中,我们在函数名后添加了 <T>
,其中 T
用来指代任意输入的类型,在后面的输入 value: T
和输出 Array<T>
中即可使用了。
还有 多个类型参数、泛型约束、泛型接口、泛型类的概念,有兴趣可以了解这篇文章