写了 TypeScript 就不想再写 JavaScript —— 真的 !
本系列主要参考的是:
感谢xcatliu的开源书籍TypeScript 入门教程
感谢zhongsp的开源书籍TypeScript Handbook 中文版
1. 第一部分
1. 是什么?
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript. Any browser. Any host. Any OS. Open source.
TypeScript是Microsoft公司注册商标。
TypeScript具有类型系统,且是JavaScript的超集。 它可以编译成普通的JavaScript 代码。TypeScript支持任意浏览器,任意环境,任意系统并且是开源的。
1. 什么是类型系统
A type system is a tractable syntactic method for proving the absence of certain program behaviors by classifying phrases according to the kinds of values they compute.
类型系统是一种易处理的句法方法,通过根据它们计算的值的类型对短语进行分类来证明某些程序行为的缺失。
第一个重点是 Proving the absence of certain program behaviors,所以我们亦可将类型检查器看做一个程序推理工具,可以静态的证明程序成立。
另一个重点是 Classifying phrases according to the kinds of values they compute,对词语(比如变量)的值的性质进行分类,比如说 TypeScript 中的 Interface 、 Class 等能力。
2. 静态类型系统是什么
增加静态这个定语,是为了和运行时的类型检查机制加以区分,强调静态类型系统是在【编译时】进行类型分析。
JavaScript 不是一个静态编译语言,不存在编译这一步骤。但从 程序推理工具 的角度来看,JavaScript 的配套中还是有不少的,比如 ESLint 这个不完备的 程序推理工具。
3. 类型系统的益处
- 侦测错误: 静态类型分析首要优点就是能尽早的发现逻辑错误,而不是上线之后才发现;
- 抽象: 类型系统的另一个优点是强化规范编程,TypeScript 提供了简便的方式定义接口。这一点在大型软件开发时尤为重要,一个系统模块可以抽象的看做一个 TypeScript 定义的接口。
- 文档: 读程序时类型标注也有用处,不止是说人在读的时候。基于类型定义 IDE 可以对我们进行很多辅助,比如找到一个函数所有的使用,编写代码时对参数进行提示等等。
> npm install -g typedoc
> typedoc --out path/to/documentation/ path/to/typescript/project/
--out 指定输出位置 <path>
--name 指定生成的文档的title名称,会显示在文档logo处 <string>
--readme 指定reamme.md的位置,用于生成首页,不指定则文档不会有首页 <path>
--module 指定模块生成方式:<commonjs or amd>
--target 指定生成文档的js版本 <ES3 or ES5>
--exclude 排除指定文件 <path>
--theme 指定文档主题样式,可以使用内置的或自定义主题 <path/to/readme|none>
--includeDeclarations 解析.d.ts类型声明文件
--externalPattern 定义应该被认为是外部的文件的模式 <pattern>
--excludeExternals 阻止生成的文档外部解析的TypeScript被记录
--hideGenerator 请勿在页面末尾打印TypeDoc链接。
--verbose 生成文档时打印详细的日志
--gaID 设置Google Analytics跟踪ID并激活跟踪代码
--gaSite 设置Google Analytics的网站名称。默认为auto
2. 为什么选择 TypeScript
1. TypeScript 增加了代码的可读性和可维护性
1. 类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了;
2. 可以在编译阶段就发现大部分错误,这总比在运行时候出错好
3. 增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等
2. TypeScript 非常包容
1. TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts
2. 自动做出类型推论
3. 可以定义从简单到复杂的一切类型 即使 TypeScript 编译报错,也可以生成 JavaScript 文件
4. 兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型
3. TypeScript 拥有活跃的社区
1. 大部分第三方库都有提供给 TypeScript 的类型定义文件
2. Google 开发的 Angular2 就是使用 TypeScript 编写的
3. ES6 的一部分特性是借鉴的 TypeScript 的(这条需要来源)
4. TypeScript 拥抱了 ES6 规范,也支持部分 ES7 草案的规范
4. TypeScript 的缺点
1. 有一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类 (Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的东西。
2. 而且它的中文资料也不多,短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需 要长期维护的项目,TypeScript 能够减少其维护成本(这条需要来源)
3. 集成到构建流程需要一些工作量 可能和一些库结合的不是很完美(这条需要举例)
整体来说,TS的 可读性、类型系统、ES6的兼容性;对比JS,会让你爱不释手。
3. 安装
npm install -g typescript
以上命令会在全局环境下安装 执行 tsc 命令了。 编译一个 TypeScript 文件很简单:
tsc hello.ts
主流的编辑器都支持 TypeScript,这里我推荐使用 Visual Studio Code,
谁用谁知道,之后再结合项目,介绍下基本使用。
第二部分
1. 基础数据类型
2. 数组、元组
1. 数组
// 两种表示方法
let list: number[] = [1, 2, 3];
let list: Array<number> = [1, 2, 3];
2. 元组
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error
3. 枚举
eum类型是对JavaScript标准数据类型的一个补充。 像C#等其它语言一样,使 用枚举类型可以为一组数值赋予友好的名字
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
默认情况下,从 0 开始为元素编号。 你也可以手动的指定成员的数值。 例如,我 们将上面的例子改成从 1 开始编号:
enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;
1. 数字枚举
enum Direction {
Up = 1,
Down,
Left,
Right
}
2. 字符串枚举
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
3. 接口
TypeScript的核心原则之一是对值所具有的结构进行类型检查。 它有时被称做“鸭式辨型法”或“结构性子类型化”。 在TypeScript里,接口的作用就是为这些类型【命名】和为你的代码或第三方代码【定义契约】。
1. 什么是接口
在面向对象语言中,【接口(Interfaces)】是一个很重要的概念,它是对行为的抽象, 而具体如何行动需要由类(classes)去实现(implements)。
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行【抽象】以外,也常用于对「对象的形状(Shape)」进行描述。
2. 第一个例子
只读、必选、可选、任意限制
// test.ts
interface Person {
// 只读属性
readonly sex: string;
// 必选
name: string;
// 可选
age?: number;
// 任意的string对应的any
[propName: string]: any;
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
第二种情况,会出现问题
interface Person {
name: string;
// 此处报错:
// 任意属性的值允许是 string ,但是可选属性 age 的值却是 number , number 不是 string 的子属性,所以报错了。
age?: number;
[propName: string]: string;
}
// 属性“age”与索引签名不兼容。不能将类型“number”分配给类型“string”。
let tom: Person = {
name: "Tom",
age: 25,
gender: "male"
};
为啥任意类型的值是any没问题? 每个属性不是独立的吗?为何可选类型必须是任意类型的子类型????
4. 泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而 在使用的时候再指定类型的一种特性。
1. 第一个例子
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 的类型。
2. <T>
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']
// 让类型推论自动推算出来
createArray(3, 'x'); // ['x', 'x', 'x']
上例中,我们在函数名后添加了 <T> ,其中 T 用来指代任意输入的类型,在后 面的输入 value: T 和输出 Array<T> 中即可使用了。
3. 第二个例子:多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
swap([7, 'seven']); // ['seven', 7]
4. 第三个例子: 泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
// index.ts(2,19): error TS2339: Property 'length' does not exis
t on type 'T'.
限制的例子
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
我们使用了 extends 形状,也就是必须包含 length
约束了泛型 属性。