TypeScript 类型系统

TypeScript 的学习资料很是多,其中也不乏不少优秀的文章和教程。可是目前为止没有一个我特别满意的。缘由有:前端

  • 它们大多数没有一个清晰的主线,而是按照 API 组织章节的,内容在逻辑上比较零散。
  • 大可能是“讲是什么,怎么用“,而不是”讲为何,讲原理“。
  • 大多数内容比较枯燥,趣味性比较低。都是干巴巴的文字,没有图片,缺少可以引发强烈共鸣的例子。

所以个人想法是作一套不一样市面上大多数的 TypeScript 学习教程。以人类认知的角度思考问题,学习 TypeScript,经过通俗易懂的例子和图片来帮助你们创建 TypeScript 世界观。 而本篇文章则是这个系列的开篇。git

系列安排:github

目录未来可能会有所调整。

注意,个人系列文章基本不会讲 API,所以须要你有必定的 TypeScript 使用基础,推荐两个学习资料。typescript

结合这两个资料和个人系列教程,掌握 TypeScript 指日可待。编程

接下来,咱们经过几个方面来从宏观的角度来看一下 TypeScript。数组

<!-- more -->app

前言

上一节的上帝视角看 TypeScript,咱们从宏观的角度来对 Typescript 进行了一个展望。之因此把那个放到开头讲是让你们有一个大致的认识,不想让你们一叶障目。当你对整个宏观层面有了必定的了解,那么对 Typescript 的理解就不会错太多。相反,一开始就是具体的概念和 API,则极可能会让你丧失都总体的基本判断。函数

实际上, Typescript 一直在不断更新迭代。一方面是由于当初许下的诺言”Typescript 是 JavaScript 的超集“(JavaScript 的特性你要同步支持,同时也要处理各类新语法带来的不兼容状况)。不单是 ECMA,社区的其余发展可能也会让 Typescript 很难受。 好比 JSX 的普遍使用就给 Typescript 泛型的使用带来了影响。学习

TypeScript 一直处于高速的迭代。除了修复平常的 bug 以外,TypeScript 也在不断发布新的功能,好比最新 4.0.0 beta 版本的标签元祖 的功能就对智能提示这块颇有用。Typescript 在社区发展方面也作的格外好,以致于它的竞争对手 Flow 被 Typescript 完美击败,这在很大程度上就是由于 Typescript 没有烂尾。现在微软在开源方向的发力是愈来愈显著了,我很期待微软接下来的表现,让咱们拭目以待。spa

变量类型和值类型

有的同窗可能有疑问, JavaScript 不是也有类型么? 它和 Typescript 的类型是一回事么?JavaScript 不是动态语言么,那么通过 Typescript 的限定会不会丧失动态语言的动态性呢?咱们继续往下看。

  • JavaScript 中的类型实际上是值的类型。实际上不只仅是 JavaScript,任何动态类型语言都是如此,这也是动态类型语言的本质。
  • Typescript 中的类型实际上是变量的类型。实际上不只仅是 Typescript,任何静态类型语言都是如此,这也是静态类型语言的本质。

记住这两句话,咱们接下来解释一下这两句话。

对于 JavaScript 来讲,一个变量能够是任意类型。

var a = 1;
a = "lucifer";
a = {};
a = [];

上面的值是有类型的。好比 1 是 number 类型,"lucifer" 是字符串类型, {} 是对象类型, [] 是数组类型。而变量 a 是没有固定类型的。

对于 Typescript 来讲, 一个变量只能接受和它类型兼容的类型的值。提及来比较拗口, 看个例子就明白了。

var a: number = 1;
a = "lucifer"; // error
var b: any = 1;
a = "lucifer"; // ok
a = {}; // ok
a = []; // ok

咱们不能将 string 类型的值赋值给变量 a, 由于 string 和 number 类型不兼容。而咱们能够将 string,Object,Array 类型的值赋值给 b,所以 它们和 any 类型兼容。简单来讲就是,一旦一个变量被标注了某种类型,那么其就只能接受这个类型以及它的子类型。

类型空间和值空间

类型和值居住在不一样的空间,一个在阳间一个在阴间。他们之间互相不能访问,甚至不知道彼此的存在。类型不能当作值来用,反之亦然。

类型空间

以下代码会报类型找不到的错:

const aa: User = { name: "lucifer", age: 17 };

这个比较好理解,咱们只须要使用 interface 声明一下 User 就行。

interface User {
  name: string;
  age: number;
}

const aa: User = { name: "lucifer", age: 17 };

也就是说使用 interface 能够在类型空间声明一个类型,这个是 Typescript 的类型检查的基础之一。

实际上类型空间内部也会有子空间。咱们能够用 namespace(老)和 module(新) 来建立新的子空间。子空间之间不能直接接触,须要依赖导入导出来交互。

值空间

好比,我用 Typescript 写出以下的代码:

const a = window.lucifer();

Typescript 会报告一个相似Property 'lucifer' does not exist on type 'Window & typeof globalThis'. 的错误。

实际上,这种错误并非类型错误,而是找不到成员变量的错误。咱们能够这样解决:

declare var lucifer: () => any;

也就是说使用 declare 能够在值空间声明一个变量。这个是 Typescript 的变量检查的基础,不是本文要讲的主要内容,你们知道就行。

明白了 JavaScript 和 TypeScript 类型的区别和联系以后,咱们就能够来进入咱们本文的主题了:类型系统

类型系统是 TypeScript 最主要的功能

TypeScript 官方描述中有一句:TypeScript adds optional types to JavaScript that support tools for large-scale JavaScript applications。实际上这也正是 Typescript 的主要功能,即给 JavaScript 添加静态类型检查。要想实现静态类型检查,首先就要有类型系统。总之,咱们使用 Typescript 的主要目的仍然是要它的静态类型检查,帮助咱们提供代码的扩展性和可维护性。所以 Typescript 须要维护一套完整的类型系统。

类型系统包括 1. 类型 和 2.对类型的使用和操做,咱们先来看类型。

类型

TypeScript 支持 JavaScript 中全部的类型,而且还支持一些 JavaScript 中没有的类型(毕竟是超集嘛)。没有的类型能够直接提供,也能够提供自定义能力让用户来本身创造。 那为何要增长 JavaScript 中没有的类型呢?我举个例子,好比以下给一个变量声明类型为 Object,Array 的代码。

const a: Object = {};
const b: Array = [];

其中:

  • 第一行代码 Typescript 容许,可是太宽泛了,咱们很可贵到有用的信息,推荐的作法是使用 interface 来描述,这个后面会讲到。
  • 第二行 Typescript 则会直接报错,缘由的本质也是太宽泛,咱们须要使用泛型来进一步约束。

对类型的使用和操做

上面说了类型和值居住在不一样的空间,一个在阳间一个在阴间。他们之间互相不能访问,甚至不知道彼此的存在。

使用 declare 和 interface or type 就是分别在两个空间编程。好比 Typescript 的泛型就是在类型空间编程,叫作类型编程。除了泛型,还有集合运算,一些操做符好比 keyof 等。值的编程在 Typescript 中更多的体现是在相似 lib.d.ts 这样的库。固然 lib.d.ts 也会在类型空间定义各类内置类型。咱们没有必要去死扣这个,只须要了解便可。

lib.d.ts 的内容主要是一些变量声明(如:window、document、math)和一些相似的接口声明(如:Window、Document、Math)。寻找代码类型(如:Math.floor)的最简单方式是使用 IDE 的 F12(跳转到定义)。

类型是如何作到静态类型检查的?

TypeScript 要想解决 JavaScript 动态语言类型太宽松的问题,就须要:

  1. 提供给变量设定类型的能力
注意是变量,不是值。
  1. 提供经常使用类型(没必要须,可是没有用户体验会极差)并能够扩展出自定义类型(必须)。
  2. 根据第一步给变量设定的类型进行类型检查,即不容许类型不兼容的赋值, 不容许使用值空间和类型空间不存在的变量和类型等。

第一个点是经过类型注解的语法来完成。即相似这样:

const a: number = 1;
Typescript 的类型注解是这样, Java 的类型注解是另外一个样子,Java 相似 int a = 1。 这个只是语法差别而已,做用是同样的。

第二个问题, Typescript 提供了诸如 lib.d.ts 等类型库文件。随着 ES 的不断更新, JavaScript 类型和全局变量会逐渐变多。Typescript 也是采用这种 lib 的方式来解决的。

(TypeScript 提供的部分 lib)

第三个问题,Typescript 主要是经过 interface,type,函数类型等打通类型空间,经过 declare 等打通值空间,并结合 binder 来进行类型诊断。关于 checker ,binder 是如何运做的,能够参考我第一篇的介绍。

接下来,咱们介绍类型系统的功能,即它能为咱们带来什么。若是上面的内容你已经懂了,那么接下来的内容会让你感到”你也不过如此嘛“。

类型系统的主要功能

  1. 定义类型以及其上的属性和方法。

好比定义 String 类型, 以及其原型上的方法和属性。

length, includes 以及 toString 是 String 的成员变量, 生活在值空间, 值空间虽然不能直接和类型空间接触,可是类型空间能够做用在值空间,从而给其添加类型(如上图黄色部分)。

  1. 提供自定义类型的能力
interface User {
  name: string;
  age: number;
  say(name: string): string;
}

这个是我自定义的类型 User,这是 Typescript 必须提供的能力。

  1. 类型兼容体系。

这个主要是用来判断类型是否正确的,上面我已经提过了,这里就不赘述了。

  1. 类型推导

有时候你不须要显式说明类型(类型注解),Typescript 也能知道他的类型,这就是类型推导结果。

const a = 1;

如上代码,编译器会自动推导出 a 的类型 为 number。还能够有连锁推导,泛型的入参(泛型的入参是类型)推导等。类型推导还有一个特别有用的地方,就是用到类型收敛。

接下来咱们详细了解下类型推导和类型收敛。

类型推导和类型收敛

let a = 1;

如上代码。 Typescript 会推导出 a 的类型为 number。

若是只会你这么写就会报错:

a = "1";

所以 string 类型的值不能赋值给 number 类型的变量。咱们可使用 Typescript 内置的 typeof 关键字来证实一下。

let a = 1;
type A = typeof a;

此时 A 的类型就是 number,证实了变量 a 的类型确实被隐式推导成了 number 类型。

有意思的是若是 a 使用 const 声明,那么 a 不会被推导为 number,而是推导为类型 1。即值只能为 1 的类型,这就是类型收敛。

const a = 1;
type A = typeof a;
经过 const ,咱们将 number 类型收缩到了 值只能为 1 的类型

实际状况的类型推导和类型收敛要远比这个复杂, 可是作的事情都是一致的。

好比这个:

function test(a: number, b: number) {
  return a + b;
}
type A = ReturnType<typeof test>;

A 就是 number 类型。 也就是 Typescript 知道两个 number 相加结果也是一个 number。所以即便你不显示地注明返回值是 number, Typescript 也能猜到。这也是为何 JavaScript 项目不接入 Typescript 也能够得到类型提示的缘由之一

除了 const 能够收缩类型, typeof, instanceof 都也能够。 缘由很简单,就是Typescript 在这个时候能够 100% 肯定你的类型了。 我来解释一下:

好比上面的 const ,因为你是用 const 声明的,所以 100% 不会变,必定永远是 1,所以类型能够收缩为 1。 再好比:

let a: number | string = 1;
a = "1";
if (typeof a === "string") {
  a.includes;
}

if 语句内 a 100% 是 string ,不能是 number。所以 if 语句内类型会被收缩为 string。instanceof 也是相似,原理如出一辙。你们只要记住Typescript 若是能够 100% 肯定你的类型,而且这个类型要比你定义的或者 Typescript 自动推导的范围更小,那么就会发生类型收缩就好了。

总结

本文主要讲了 Typescript 的类型系统。 Typescript 和 JavaScript 的类型是很不同的。从表面上来看, TypeScript 的类型是 JavaScript 类型的超集。可是从更深层次上来讲,二者的本质是不同的,一个是值的类型,一个是变量的类型。

Typescript 空间分为值空间和类型空间。两个空间不互通,所以值不能当成类型,类型不能当成值,而且值和类型不能作运算等。不过 TypeScript 能够将二者结合起来用,这个能力只有 TypeScript 有, 做为 TypeScript 的开发者的你没有这个能力,这个我在第一节也简单介绍了。

TypeScript 既会对变量存在与否进行检查,也会对变量类型进行兼容检查。所以 TypeScript 就须要定义一系列的类型,以及类型之间的兼容关系。默认状况,TypeScript 是没有任何类型和变量的,所以你使用 String 等都会报错。TypeScript 使用库文件来解决这个问题,最经典的就是 lib.d.ts。

TypeScript 已经作到了足够智能了,以致于你不须要写类型,它也能猜出来,这就是类型推导和类型收缩。固然 TypeScript 也有一些功能,咱们以为应该有,而且也是能够作到的功能空缺。可是我相信随着 TypeScript 的逐步迭代(截止本文发布,TypeScript 刚刚发布了 4.0.0 的 beta 版本),必定会愈来愈完善,用着愈来愈舒服的。

咱们每一个项目的须要是不同的, 简单的基本类型确定没法知足多样的项目需求,所以咱们必须支持自定义类型,好比 interface, type 以及复杂一点的泛型。固然泛型很大程度上是为了减小样板代码而生的,和 interface , type 这种刚需不太同样。

有了各类各样的类型以及类型上的成员变量,以及成员变量的类型,再就加上类型的兼容关系,咱们就能够作类型检查了,这就是 TypeScript 类型检查的基础。TypeScript 内部须要维护这样的一个关系,并对变量进行类型绑定,从而给开发者提供类型分析服务。

关注我

你们也能够关注个人公众号《脑洞前端》获取更多更新鲜的前端硬核文章,带你认识你不知道的前端。

公众号【 力扣加加
知乎专栏【 Lucifer - 知乎

点关注,不迷路!

相关文章
相关标签/搜索