浅学易懂的ts
Lin. 切图仔

什么是 TypeScript?

TypeScript 是微软开发的一个开源的编程语言,是 JavaScript 的一个超集,而且本质上是向 JavaScript 添加了可选的静态类型和基于类的面向对象编程

TypeScript 的特点

  • TypeScript 扩展了 JavaScript 的语法,所以任何现有的 JavaScript 程序可以运行在 TypeScript 环境中
  • 并且最终会被编译成 JavaScript
  • 会在编译期间捕捉 JavaScript 语法错误

TypeScript 与 JavaScript 的区别

ts与js的区别

获取 TypeScript

可以使用 npm 包管理器来安装。

安装

1
$ npm install -g typescript

验证安装

1
2
$ tsc -v 
# Version xxx

编译 TS 文件

1
2
$ tsc hello.ts
# hello.ts => hello.js

体验 TS

先新建一个 hello.ts 文件:

1
2
3
4
5
function greet(val: string) {
return 'Hello, ' + val;
}

console.log(greet("TypeScript"));

然后执行 tsc hello.ts 命令,之后会生成一个编译好的文件 hello.js

1
2
3
4
5
"use strict";
function greet(val) {
return 'Hello, ' + val;
}
console.log(greet("TypeScript"));

我们观察编译后的结果发现, val 参数的类型信息在编译后被擦除了。因为 TypeScript 只会在编译阶段对类型进行静态检查,如果发现有错误,编译时就会报错。而在运行时,编译生成的 JS 与普通的 JavaScript 文件一样,并不会进行类型检查。

学习 TypeScript

TypeScript 基础类型

Boolean 类型

1
2
let isBoolean: boolean = false;
// ES5:var isBoolean = false;

Number 类型

1
2
let count: number = 10;
// ES5:var count = 10;

String 类型

1
2
let name: string = "semliker";
// ES5:var name = 'semlinker';

Symbol 类型

1
2
3
4
5
const sym = Symbol();
let obj = {
[sym]: "semlinker",
};
console.log(obj[sym]); // semlinker

Array 类型

1
2
3
4
5
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];

Enum 类型

Enum 也称枚举,使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。

TypeScript 支持数字字符串枚举。

数字枚举

1
2
3
4
5
6
7
8
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}

let dir: Direction = Direction.NORTH;

默认情况下,NORTH 的初始值为 0,其余的成员会自动 +1 增长。简单就是说,Direction.SOUTH 的值为 1,Direction.EAST 的值为 2,Direction.WEST 的值为 3。

对应的 ES5 代码如下:

1
2
3
4
5
6
7
8
9
"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;

也可以设置默认值,比如:

1
2
3
4
5
6
enum Direction {
NORTH = 3,
SOUTH,
EAST,
WEST,
}

那其余成员从 3 开始自动增长

字符串枚举

字符串枚举里,每个成员都必须用字符串字面量,或另外一个字符串枚举成员进行初始化。

1
2
3
4
5
6
enum Direction {
NORTH = "NORTH",
SOUTH = "SOUTH",
EAST = "EAST",
WEST = "WEST",
}

对应的 ES5 代码如下:

1
2
3
4
5
6
7
8
"use strict";
var Direction;
(function (Direction) {
Direction["NORTH"] = "NORTH";
Direction["SOUTH"] = "SOUTH";
Direction["EAST"] = "EAST";
Direction["WEST"] = "WEST";
})(Direction || (Direction = {}));

通过观察数字枚举和字符串枚举的编译结果,我们发现数字枚举除了支持 从成员名称到成员值 的普通映射之外,它还支持 从成员值到成员名称 的反向映射:

1
2
3
4
5
6
7
8
9
enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}

let dirName = Direction[0]; // NORTH
let dirVal = Direction["NORTH"]; // 0

常量枚举

常量枚举使用 const 关键字修饰的枚举,常量枚举会使用内联语法,不会为枚举类型编译生成任何 JavaScript。为了更好地理解这句话,我们来看一个具体的例子:

1
2
3
4
5
6
7
8
const enum Direction {
NORTH,
SOUTH,
EAST,
WEST,
}

let dir: Direction = Direction.NORTH;
1
2
"use strict";
var dir = 0 /* NORTH */;

异构枚举

异构枚举的成员值是数字和字符串的混合:

1
2
3
4
5
6
7
8
enum Enum {
A,
B,
C = "C",
D = "D",
E = 8,
F,
}

ES5代码如下:

1
2
3
4
5
6
7
8
9
10
"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 = {}));

通过观察上述生成的 ES5 代码,我们可以发现数字枚举相对字符串枚举多了 “反向映射”:

1
2
console.log(Enum.A) //输出:0
console.log(Enum[0]) // 输出:A

Any类型

在 TypeScript 中,任何类型都可以被归为 any 类型。这让 any 类型成为了类型系统的顶级类型(也被称作全局超级类型)。

1
2
3
let notSure: any = 666;
notSure = "str";
notSure = false;

any 类型本质上是类型系统的一个逃逸舱,可以跳过类型检测,这给了开发者很大的自由:TypeScript 允许我们对 any 类型的值执行任何操。比如:

1
2
3
4
5
6
7
let value: any;

value.foo.bar; // OK
value.trim(); // OK
value(); // OK
new value(); // OK
value[0][1]; // OK

使用 any 类型,在带来自由的同时,破坏了 TypeScript 提供的大量的保护机制。

Unknown 类型

TypeScript 3.0 引入了 unknown 类型。 unknown 类型是为了解决 any 类型带来的问题。

就像所有类型都可以赋值给 any,所有类型也都可以赋值给 unknown。这使得 unknown 成为 TypeScript 类型系统的另一种顶级类型(另一种是 any)。

1
2
3
4
5
6
7
8
9
10
11
12
let value: unknown;

value = true; // OK
value = 42; // OK
value = "Hello World"; // OK
value = []; // OK
value = {}; // OK
value = Math.random; // OK
value = null; // OK
value = undefined; // OK
value = new TypeError(); // OK
value = Symbol("type"); // OK

但是前面又说了 unknown 类型是为了解决 any 类型带来的问题,也没体现出来呀?但是,当我们尝试将类型为 unknown 的值赋值给其他类型的变量时会发生什么?

1
2
3
4
5
6
7
8
9
10
let value: unknown;

let value1: unknown = value; // OK
let value2: any = value; // OK
let value3: boolean = value; // Error
let value4: number = value; // Error
let value5: string = value; // Error
let value6: object = value; // Error
let value7: any[] = value; // Error
let value8: Function = value; // Error

unknown 类型只能被赋值给 any 类型和 unknown 类型本身。直观地说,这是有道理的:只有能够保存任意类型值的容器才能保存 unknown 类型的值。毕竟我们不知道变量 value 中存储了什么类型的值。

现在让我们看看当我们尝试对类型为 unknown 的值执行操作时会发生什么。以下是我们在之前 any 章节看过的相同操作:

1
2
3
4
5
6
7
let value: unknown;

value.foo.bar; // Error
value.trim(); // Error
value(); // Error
new value(); // Error
value[0][1]; // Error

value 变量类型设置为 unknown 后,这些操作都不再被认为是类型正确的。通过将 any 类型改变为 unknown 类型,我们已将允许所有更改的默认设置,更改为禁止任何更改。

Tuple 类型

众所周知,数组一般由同种类型的值组成,但有时我们需要在单个变量中存储不同类型的值,这时候我们就可以使用元组。在 JavaScript 中是没有元组的,元组是 TypeScript 中特有的类型,其工作方式类似于数组。

元组可用于定义具有有限数量的未命名属性的类型。每个属性都有一个关联的类型。使用元组时,必须提供每个属性的值。为了更直观地理解元组的概念,我们来看一个具体的例子:

1
2
let tupleType: [string, boolean];
tupleType = ["semlinker", true];

可以通过数组下标来访问数据:

1
2
console.log(tupleType[0]); // semlinker
console.log(tupleType[1]); // true

在元组初始化的时候,如果出现类型不匹配的话,比如:

1
tupleType = [true, "semlinker"];

此时,TypeScript 编译器会提示以下错误信息:

1
2
[0]: Type 'true' is not assignable to type 'string'.
[1]: Type 'string' is not assignable to type 'boolean'.

如果提供的属性值不匹配,也会出现错误:

1
tupleType = ["semlinker"];

此时,TypeScript 编译器会提示以下错误信息:

1
Property '1' is missing in type '[string]' but required in type '[string, boolean]'.

Void 类型

一般是用来定义函数返回值的。

某种程度上来说,void 类型像是与 any 类型相反,它表示没有任何类型。当一个函数没有返回值时,你通常会见到其返回值类型是 void:

1
2
3
4
// 声明函数返回值为void
function warnUser(): void {
console.log("This is my warning message");
}

对应的ES5代码:

1
2
3
4
"use strict";
function warnUser() {
console.log("This is my warning message");
}

需要注意的是,声明一个 void 类型的变量没有什么作用,因为在严格模式下,它的值只能为 undefined

1
let unusable: void = undefined;

Null 和 Undefined 类型

1
2
let u: undefined = undefined;
let n: null = null;

object, Object 和 {} 类型

object 类型

object 类型是:TypeScript 2.2 引入的新类型,它用于表示非原始类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor {
create(o: object | null): any;
// ...
}

const proto = {};

Object.create(proto); // OK
Object.create(null); // OK
Object.create(undefined); // Error
Object.create(1337); // Error
Object.create(true); // Error
Object.create("oops"); // Error

Object 类型

Object 类型:它是所有 Object 类的实例的类型,它由以下两个接口来定义:

  • Object 接口定义了 Object.prototype 原型对象上的属性;
1
2
3
4
5
6
7
8
9
10
// node_modules/typescript/lib/lib.es5.d.ts
interface Object {
constructor: Function;
toString(): string;
toLocaleString(): string;
valueOf(): Object;
hasOwnProperty(v: PropertyKey): boolean;
isPrototypeOf(v: Object): boolean;
propertyIsEnumerable(v: PropertyKey): boolean;
}
  • ObjectConstructor 接口定义了 Object 类的属性。
1
2
3
4
5
6
7
8
9
10
11
12
// node_modules/typescript/lib/lib.es5.d.ts
interface ObjectConstructor {
/** Invocation via `new` */
new(value?: any): Object;
/** Invocation via function calls */
(value?: any): any;
readonly prototype: Object;
getPrototypeOf(o: any): any;
// ···
}

declare var Object: ObjectConstructor;

{} 类型

{} 类型描述了一个没有成员的对象。当你试图访问这样一个对象的任意属性时,TypeScript 会产生一个编译时错误。

1
2
3
4
5
// Type {}
const obj = {};

// Error: Property 'prop' does not exist on type '{}'.
obj.prop = "semlinker";

但是,你仍然可以使用在 Object 类型上定义的所有属性和方法,这些属性和方法可通过 JavaScript 的原型链隐式地使用:

1
2
3
4
5
// Type {}
const obj = {};

// "[object Object]"
obj.toString();

Never 类型

never 类型表示的是永不存在的值的类型。 例如,never 类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型。

1
2
3
4
5
6
7
8
// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
throw new Error(message);
}

function infiniteLoop(): never {
while (true) {}
}

在 TypeScript 中,可以利用 never 类型的特性来实现全面性检查,具体示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
type Foo = string | number;

function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === "string") {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === "number") {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo;
}
}

注意在 else 分支里面,我们把收窄为 never 的 foo 赋值给一个显示声明的 never 变量。如果一切逻辑正确,那么这里应该能够编译通过。但是假如后来有一天你的同事修改了 Foo 的类型:

1
type Foo = string | number | boolean;

然而他忘记同时修改 controlFlowAnalysisWithNever 方法中的控制流程,这时候 else 分支的 foo 类型会被收窄为 boolean 类型,导致无法赋值给 never 类型,这时就会产生一个编译错误。通过这个方式,我们可以确保

controlFlowAnalysisWithNever 方法总是穷尽了 Foo 的所有可能类型。 通过这个示例,我们可以得出一个结论:使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。

TypeScript 断言

类型断言

一般数据的类型是我们主动定义,或者是程序推导出来的。但是有时候,我们想手动去更改类型,告诉编译器我要用这个类型做操作。类型断言好比其他语言里的类型转换,但是不进行特殊的数据检查和解构。它没有运行时的影响,只是在编译阶段起作用。

类型断言有两种形式:

“尖括号” 语法

1
2
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

as 语法

1
2
let someValue: any = "this is a string";
let strLength: number = (someValue as string).length;
  • 本文标题:浅学易懂的ts
  • 本文作者:Lin.
  • 创建时间:2021-11-20 13:27:44
  • 本文链接:http://www.linzefan.top/hexo-blogs2021/11/20/浅学易懂的ts/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论