01 TypeScript是什么
1.1 TypeScript 与 JavaScript 的区别
TypeScript | JavaScript |
---|---|
JavaScript 的超集用于解决大型项目的代码复杂性 | 一种脚本语言,用于创建动态网页。 |
社区的支持仍在增长,而且还不是很大 | 大量的社区支持以及大量文档和解决问题的支持 |
可以在编译期间发现并纠正错误 | 作为一种解释型语言,只能在运行时发现错误 |
支持 ES3,ES4,ES5 和 ES6 等 | 不支持编译其他 ES3,ES4,ES5 或 ES6 功能 |
强类型,支持静态和动态类型 | 弱类型,没有静态类型选项 |
支持模块、泛型和接口 | 不支持模块,泛型或接口 |
最终被编译成 JavaScript 代码,使浏览器可以理解 | 可以直接在浏览器中使用 |
1.2 获取 TypeScript
命令行的 TypeScript 编译器可以使用 Node.js 包来安装。
1.安装 TypeScript
$ npm install -g typescript
2.编译 TypeScript 文件
$ tsc helloworld.ts
helloworld.ts => helloworld.js
02 TypeScript基础类型
2.1 Boolean 类型
let isDone: boolean = false;
// ES5:var isDone = false;
2.2 Number 类型
let count: number = 10;
// ES5:var count = 10;
2.3 String 类型
let name: string = "Semliker";
// ES5:var name = 'Semlinker';
2.4 Array 类型
let list: number[] = [1, 2, 3];
// ES5:var list = [1,2,3];
let list: Array<number> = [1, 2, 3]; // Array<number>泛型语法
// ES5:var list = [1,2,3];
2.5 Enum 类型
使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript 支持数字的和基于字符串的枚举。
1.数字枚举
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}
let dir: Direction = Direction.NORTH;
上面的枚举示例代码经过编译后会生成以下代码:
"use strict";
var Direction;
(function (Direction) {
Direction[(Direction["NORTH"] = 0)] = "NORTH";
Direction[(Direction["SOUTH"] = 1)] = "SOUTH";
Direction[(Direction["EAST"] = 2)] = "EAST";
Direction[(Direction["WEST"] = 3)] = "WEST";
})(Direction || (Direction = {}));
var dir = Direction.NORTH;
2.字符串枚举
在一个字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。
enum Direction {
NORTH = "NORTH",
SOUTH = "SOUTH",
EAST = "EAST",
WEST = "WEST",
}
以上代码对于的 ES5 代码如下:
"use strict";
var Direction;
(function (Direction) {
Direction["NORTH"] = "NORTH";
Direction["SOUTH"] = "SOUTH";
Direction["EAST"] = "EAST";
Direction["WEST"] = "WEST";
})(Direction || (Direction = {}));
3.异构枚举
异构枚举的成员值是数字和字符串的混合:
enum Enum {
A,
B,
C = "C",
D = "D",
E = 8,
F,
}
以上代码对于的 ES5 代码如下:
"use strict";
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
Enum[Enum["B"] = 1] = "B";
Enum["C"] = "C";
Enum["D"] = "D";
Enum[Enum["E"] = 8] = "E";
Enum[Enum["F"] = 9] = "F";
})(Enum || (Enum = {}));
03 联合类型和类型别名
3.1 联合类型
联合类型通常与 null 或 undefined 一起使用:
const sayHello = (name: string | undefined) => {
/* ... */
};
这里 name
的类型是 string | undefined
意味着可以将 string
或 undefined
的值传递给sayHello
函数。
3.2 可辨识联合
TypeScript 可辨识联合(Discriminated Unions)类型,也称为代数数据类型或标签联合类型。它包含 3 个要点:可辨识、联合类型和类型守卫。
这种类型的本质是结合联合类型和字面量类型的一种类型保护方法。如果一个类型是多个类型的联合类型,且多个类型含有一个公共属性,那么就可以利用这个公共属性,来创建不同的类型保护区块。
1.可辨识
可辨识要求联合类型中的每个元素都含有一个单例类型属性,比如:
enum CarTransmission {
Automatic = 200,
Manual = 300
}
interface Motorcycle {
vType: "motorcycle"; // discriminant
make: number; // year
}
interface Car {
vType: "car"; // discriminant
transmission: CarTransmission
}
interface Truck {
vType: "truck"; // discriminant
capacity: number; // in tons
}
在上述代码中,我们分别定义了 Motorcycle
、 Car
和 Truck
三个接口,在这些接口中都包含一个 vType
属性,该属性被称为可辨识的属性,而其它的属性只跟特性的接口相关。
2.联合类型
基于前面定义了三个接口,我们可以创建一个 Vehicle
联合类型:
type Vehicle = Motorcycle | Car | Truck;
现在我们就可以开始使用 Vehicle
联合类型,对于 Vehicle
类型的变量,它可以表示不同类型的车辆。
3.3 类型别名
类型别名用来给一个类型起个新名字。
type Message = string | string[];
let greet = (message: Message) => {
// ...
};
04 TypeScript函数
4.1 TypeScript 函数与 JavaScript 函数的区别
4.2 箭头函数
1.常见语法
myBooks.forEach(() => console.log('reading'));
myBooks.forEach(title => console.log(title));
myBooks.forEach((title, idx, arr) =>
console.log(idx + '-' + title);
);
myBooks.forEach((title, idx, arr) => {
console.log(idx + '-' + title);
});
2.使用示例
// 未使用箭头函数
function Book() {
let self = this;
self.publishDate = 2016;
setInterval(function () {
console.log(self.publishDate);
}, 1000);
}
// 使用箭头函数
function Book() {
this.publishDate = 2016;
setInterval(() => {
console.log(this.publishDate);
}, 1000);
}
4.3 参数类型和返回类型
function createUserId(name: string, id: number): string {
return name + id;
}
4.4 函数类型
let IdGenerator: (chars: string, nums: number) => string;
function createUserId(name: string, id: number): string {
return name + id;
}
IdGenerator = createUserId;
4.5 函数重载
函数重载或方法重载是使用相同名称和不同参数数量或类型创建多个方法的一种能力。要解决前面遇到的问题,方法就是为同一个函数提供多个函数类型定义来进行函数重载,编译器会根据这个列表去处理函数的调用。
function add(a: number, b: number): number;
function add(a: string, b: string): string;
function add(a: string, b: number): string;
function add(a: number, b: string): string;
function add(a: Combinable, b: Combinable) {
if (typeof a === "string" || typeof b === "string") {
return a.toString() + b.toString();
}
return a + b;
}
- 方法重载是指在同一个类中方法同名,参数不同(参数类型不同、参数个数不同或参数个数相同时参数的先后顺序不同),调用时根据实参的形式,选择与它匹配的方法执行操作的一种技术。
- 所以类中成员方法满足重载的条件是:**在同一个类中,方法名相同且参数列表不同。**下面我们来举一个成员方法重载的例子:
class Calculator {
add(a: number, b: number): number;
add(a: string, b: string): string;
add(a: string, b: number): string;
add(a: number, b: string): string;
add(a: Combinable, b: Combinable) {
if (typeof a === "string" || typeof b === "string") {
return a.toString() + b.toString();
}
return a + b;
}
}
const calculator = new Calculator();
const result = calculator.add(“Semlinker”, " Kakuqo");
4.6 可选参数及默认参数
// 可选参数
function createUserId(name: string, id: number, age?: number): string {
return name + id;
}
// 默认参数
function createUserId(
name: string = "Semlinker",
id: number,
age?: number
): string {
return name + id;
}
在声明函数时,可以通过 ?
号来定义可选参数,比如 age?: number
这种形式。在实际使用时,需要注意的是可选参数要放在普通参数的后面,不然会导致编译错误。
05 TypeScript数组
5.1 数组解构
let x: number; let y: number; let z: number;
let five_array = [0,1,2,3,4];
[x,y,z] = five_array;
5.2 数组展开运算符
let two_array = [0, 1];
let five_array = [...two_array, 2, 3, 4];
5.3 数组遍历
let colors: string[] = ["red", "green", "blue"];
for (let i of colors) {
console.log(i);
}
06 TypeScript对象
6.1 对象解构
let person = {
name: "Semlinker",
gender: "Male",
};
let { name, gender } = person;
6.2 对象展开运算符
typescript
let person = {
name: "Semlinker",
gender: "Male",
address: "Xiamen",
};
// 组装对象
let personWithAge = { ...person, age: 33 };
// 获取除了某些项外的其它项
let { name, ...rest } = person;
07 TypeScript接口
TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,
也常用于对象的形状进行描述。
7.1 对象的形状
interface Person {
name: string;
age: number;
}
let Semlinker: Person = {
name: "Semlinker",
age: 33,
};
7.2 可选 | 只读属性
interface Person {
readonly name: string;
age?: number;
}
- 只读属性用于限制只能在对象刚刚创建的时候修改其值。
- 此外 TypeScript 还提供了
ReadonlyArray<T>
类型,它与Array<T>
相似,只是把所有可变方法去掉了,因此可以确保数组创建后再也不能被修改。
let a: number[] = [1, 2, 3, 4];
let ro: ReadonlyArray<number> = a;
ro[0] = 12; // error!
ro.push(5); // error!
ro.length = 100; // error!
a = ro; // error!
08 TypeScript类
8.1 类的属性与方法
在面向对象语言中,类是一种面向对象计算机编程语言的构造,是创建对象的蓝图,描述了所创建的对象共同的属性和方法。
在 TypeScript 中,我们可以通过 Class
关键字来定义一个类:
class Greeter {
// 静态属性
static cname: string = "Greeter";
// 成员属性
greeting: string;
// 构造函数 - 执行初始化操作
constructor(message: string) {
this.greeting = message;
}
// 静态方法
static getClassName() {
return "Class name is Greeter";
}
// 成员方法
greet() {
return "Hello, " + this.greeting;
}
}
let greeter = new Greeter("world");
那么成员属性与静态属性,成员方法与静态方法有什么区别呢?这里无需过多解释,我们直接看一下以下编译生成的 ES5 代码:
"use strict";
var Greeter = /** @class */ (function () {
// 构造函数 - 执行初始化操作
function Greeter(message) {
this.greeting = message;
}
// 静态方法
Greeter.getClassName = function () {
return "Class name is Greeter";
};
// 成员方法
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
// 静态属性
Greeter.cname = "Greeter";
return Greeter;
}());
var greeter = new Greeter("world");
8.2 访问器
在 TypeScript 中,我们可以通过 getter
和 setter
方法来实现数据的封装和有效性校验,防止出现异常数据。
let passcode = "Hello TypeScript";
class Employee {
private _fullName: string;
get fullName(): string {
return this._fullName;
}
set fullName(newName: string) {
if (passcode && passcode == "Hello TypeScript") {
this._fullName = newName;
} else {
console.log("Error: Unauthorized update of employee!");
}
}
}
let employee = new Employee();
employee.fullName = "Semlinker";
if (employee.fullName) {
console.log(employee.fullName);
}
8.3 类的继承
继承 (Inheritance) 是一种联结类与类的层次模型。指的是一个类(称为子类、子接口)继承另外的一个类(称为父类、父接口)的功能,并可以增加它自己的新功能的能力,继承是类与类或者接口与接口之间最常见的关系。
继承是一种 is-a 关系:
在 TypeScript 中,我们可以通过 extends 关键字来实现继承:
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
class Snake extends Animal {
constructor(name: string) {
super(name);
}
move(distanceInMeters = 5) {
console.log("Slithering...");
super.move(distanceInMeters);
}
}
let sam = new Snake("Sammy the Python");
sam.move();
8.4 ECMAScript 私有字段
在 TypeScript 3.8 版本就开始支持ECMAScript 私有字段,使用方式如下:
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}!`);
}
}
let semlinker = new Person("Semlinker");
semlinker.#name;
// ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.
与常规属性(甚至使用 private
修饰符声明的属性)不同,私有字段要牢记以下规则:
- 私有字段以
#
字符开头,有时我们称之为私有名称; - 每个私有字段名称都唯一地限定于其包含的类;
- 不能在私有字段上使用 TypeScript 可访问性修饰符(如 public 或 private);
- 私有字段不能在包含的类之外访问,甚至不能被检测到。 二级标题