什么是 TypeScript
TypeScript 是微软开发的 JavaScript 的超集,TypeScript兼容JavaScript,可以载入JavaScript代码然后运行。TypeScript与JavaScript相比进步的地方 包括:加入注释,让编译器理解所支持的对象和函数,编译器会移除注释,不会增加开销;增加一个完整的类结构,使之更新是传统的面向对象语言。
TypeScript 优势
TypeScript 主要特点包括:
- TypeScript 是微软推出的开源语言,使用 Apache 授权协议
- TypeScript 是 JavaScript 的超集
- TypeScript 增加了可选类型、类和模块
- TypeScript 可编译成可读的、标准的 JavaScript
- TypeScript 支持开发大规模 JavaScript 应用
- TypeScript 设计用于开发大型应用,并保证编译后的 JavaScript 代码兼容性
- TypeScript 扩展了 JavaScript 的语法,因此已有的 JavaScript 代码可直接与 TypeScript 一起运行无需更改
- TypeScript 文件扩展名是 ts,而 TypeScript 编译器会编译成 js 文件
- TypeScript 语法与 JScript .NET 相同
- TypeScript 易学易于理解
JavaScript 与 TypeScript 的区别
- TypeScript 是 JavaScript 的超集,扩展了 JavaScript 的语法,因此现有的 JavaScript 代码可与 TypeScript 一起工作无需任何修改,TypeScript 通过类型注解提供编译时的静态类型检查。TypeScript 可处理已有的 JavaScript 代码,并只对其中的 TypeScript 代码进行编译。
- TypeScript 是 JavaScript 的类型的超集,它可以编译成纯 JavaScript。编译出来的 JavaScript 可以运行在任何浏览器上。TypeScript 编译工具可以运行在任何服务器和任何系统上。TypeScript 是开源的。
- TypeScript 增加了代码的可读性和可维护性
类型系统实际上是最好的文档,大部分的函数看看类型的定义就可以知道如何使用了,可以在编译阶段就发现大部分错误,这总比在运行时候出错好,增强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等。 - TypeScript 非常包容
TypeScript 是 JavaScript 的超集,.js 文件可以直接重命名为 .ts 即可,即使不显式的定义类型,也能够自动做出类型推论,可以定义从简单到复杂的一切类型,即使 TypeScript 编译报错,也可以生成 JavaScript 文件,兼容第三方库,即使第三方库不是用 TypeScript 写的,也可以编写单独的类型文件供 TypeScript 读取。
TypeScript 对JavaScript的改进主要是静态类型检查
- 静态类型检查可以做到early fail,即你编写的代码即使没有被执行到,一旦你编写代码时发生类型不匹配,语言在编译阶段(解释执行也一样,可以在运行前)即可发现。针对大型应用,测试调试分支覆盖困难,很多代码并不一定能够在所有条件下执行到。而假如你的代码简单到任何改动都可以从UI体现出来,这确实跟大型应用搭不上关系,那么静态类型检查确实没什么作用。
- 静态类型对阅读代码是友好的,比如我们举个例子 jQuery API Documentation 这是大家都非常喜欢用的jQuery.ajax,在这份文档中,详尽地解释了类型为object的唯一一个参数settings,它是如此之复杂,如果没有文档,我们只看这个函数声明的话,根本不可能有人把这个用法猜对。针对大型应用,方法众多,调用关系复杂,不可能每个函数都有人编写细致的文档,所以静态类型就是非常重要的提示和约束。而假如你的代码像jQuery这样所有函数基本全是API,根本没什么内部函数,而且逻辑关系看起来显而易见,这确实跟大型应用搭不上关系,那么静态类型对阅读代码确实也没什么帮助。总的来说,现代编程语言设计,很多特性已经有非常成熟的理论支持了,如果我们重视计算机基础,那么一些语言的适用场景就像是拼积木,可以用几句话概括。像是TS对JS这样,只是单一特性变化。
TypeScript 的缺点
- 有一定的学习成本,需要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的东西
- 短期可能会增加一些开发成本,毕竟要多写一些类型的定义,不过对于一个需要长期维护的项目,TypeScript 能够减少其维护成本
- 集成到构建流程需要一些工作量
- 可能和一些库结合的不是很完美
如何使用
安装Typescript
npm install -g typescript
编译.ts文件-->javescript
编译 tsc xxx.ts
实时编译 tsc xxx.ts -w
语法简介
类型
js原始数据类型包括:布尔值、数值、字符串、null、undefined
在js基础上新加了any,void类型
在 TypeScirpt 中,可以用 void 表示没有任何返回值的函数
声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null
undefined 和 null 是所有类型的子类型
如果是 any 类型,则允许被赋值为任意类型
声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值
变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
如果没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型
如果是一个普通类型,在赋值过程中改变类型是不被允许的
对象的类型——接口
在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动需要由类(classes)去实现(implements)
赋值的时候,变量的形状必须和接口的形状保持一致
未定义任意属性时,多些、少些属性都是不被允许的
一旦定义了任意属性,那么确定属性和可选属性都必须是它的子属性
只读的约束存在于第一次给对象赋值的时候,而不是第一次给只读属性赋值的时候
interface Person {
readonly id: number; // 只读属性
name: string; // 确定属性
age?: number; // 可选属性
[propName: string]: any; // 任意属性
}
let tom: Person = {
name: 'Tom',
gender: 'male'
};
函数的类型
一个函数有输入和输出,要在 TypeScript 中对其进行约束,需要把输入和输出都考虑到,其中函数声明的类型定义较简单:
function sum(x: number, y: number): number {
return x + y;
}
也可以使用接口的方式来定义一个函数需要符合的形状:
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc;
mySearch = function(source: string, subString: string) {
return source.search(subString) !== -1;
}
// 编译前
function buildName(firstName: string, lastName?: string) {
return lastName ? firstName + ' ' + lastName : firstName
}
// 编译后
function buildName(firstName, lastName) {
return lastName ? firstName + ' ' + lastName : firstName;
}
输入多余的(或者少于要求的)参数,是不被允许的
可选参数后面不允许再出现必须参数
TypeScript 会将添加了默认值的参数识别为可选参数
剩余参数
// 编译前
function push(array, ...items) {
items.forEach(function(item) {
array.push(item);
});
}
// 编译后
function push(array) {
var items = [];
for (var _i = 1; _i < arguments.length; _i++) {
items[_i - 1] = arguments[_i];
}
items.forEach(function (item) {
array.push(item);
});
}
元组
数组合并了相同类型的对象,而元组(Tuple)合并了不同类型的对象
元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同
let xcatliu: [string, number] = ['Xcat Liu', 25];
当赋值给越界的元素时,它类型会被限制为元组中每个类型的联合类型:
let xcatliu: [string, number];
xcatliu = ['Xcat Liu', 25, 'http://xcatliu.com/'];
枚举
枚举(Enum)类型用于取值被限定在一定范围内的场景
枚举使用 enum 关键字来定义
枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射
一个枚举类型可以包含零个或多个枚举成员。 枚举成员具有一个数字值,它可以是 常数或是计算得出的值
enum Color {Red, Green, Blue = "blue".length};
enum Days {Sun, Mon, Tue, Wed, Thu, Fri, Sat};
// 编译后
var Days;
(function (Days) {
Days[Days["Sun"] = 0] = "Sun";
Days[Days["Mon"] = 1] = "Mon";
Days[Days["Tue"] = 2] = "Tue";
Days[Days["Wed"] = 3] = "Wed";
Days[Days["Thu"] = 4] = "Thu";
Days[Days["Fri"] = 5] = "Fri";
Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));
// Days --> { '0': 'Sun', '1': 'Mon', '2': 'Tue', '3': 'Wed', '4': 'Thu', '5': 'Fri', '6': 'Sat', Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 }
给枚举项手动赋值: enum Days {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};
如果未手动赋值的枚举项与手动赋值的重复了,TypeScript 是不会察觉到这一点的
当满足以下条件时,枚举成员被当作是常数:
- 不具有初始化函数并且之前的枚举成员是常数。在这种情况下,当前枚举成员的值为上一个枚举成员的值加 1。但第一个枚举元素是个例外。如果它没有初始化方法,那么它的初始值为 0
- 枚举成员使用常数枚举表达式初始化。常数枚举表达式是 TypeScript 表达式的子集,它可以在编译阶段求值
当一个表达式满足下面条件之一时,它就是一个常数枚举表达式:
- 数字字面量
- 引用之前定义的常数枚举成员(可以是在不同的枚举类型中定义的)如果这个成员是在同一个枚举类型中定义的,可以使用非限定名来引用
- 带括号的常数枚举表达式
- +, -, ~ 一元运算符应用于常数枚举表达式
- +, -, *, /, %, <<, >>, >>>, &, |, ^ 二元运算符,常数枚举表达式做为其一个操作对象。若常数枚举表达式求值后为NaN或Infinity,则会在编译阶段报错
所有其它情况的枚举成员被当作是需要计算得出的值
泛型
泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法
interface Lengthwise {
length: number;
}
function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length);
return arg;
}
也可以使用含有泛型的接口来定义函数的形状:
interface CreateArrayFunc {
<T>(length: number, value: T): Array<T>;
}
let createArray: CreateArrayFunc;
createArray = function<T>(length: number, value: T): Array<T> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
类
TypeScript 除了实现了所有 ES6 中的类的功能以外,还添加了一些新的用法
TypeScript 可以使用三种访问修饰符(Access Modifiers),分别是 public、private 和 protected
- public 修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是 public 的
- private 修饰的属性或方法是私有的,不能在声明它的类的外部访问
- protected 修饰的属性或方法是受保护的,它和 private 类似,区别是它在子类中也是允许被访问的
abstract 用于定义抽象类和其中的抽象方法
编译前
abstract class Animal {
public name;
public constructor(name) {
this.name = name;
}
public abstract sayHi();
}
class Cat extends Animal {
public sayHi() {
console.log(`Meow, My name is ${this.name}`);
}
}
let cat = new Cat('Tom');
编译后
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var Animal = (function () {
function Animal(name) {
this.name = name;
}
return Animal;
}());
var Cat = (function (_super) {
__extends(Cat, _super);
function Cat() {
_super.apply(this, arguments);
}
Cat.prototype.sayHi = function () {
console.log('Meow, My name is ' + this.name);
};
return Cat;
}(Animal));
var cat = new Cat('Tom');
更多阅读资料