为什么我总学不好TS?

大家好,我卡颂。

有没有同学 学TS的步骤和我一样:

  1. 先看TS文档(或各种入门教材),学学各种类型的定义

  2. 为现有项目中的JS代码增加类型

  3. 随着增加的类型越来越多,类型报错越来越多,不得已改为any类型,或者增加// @ts-ignore注释

最后,使用TS的成本(改各种类型报错耽误的时间)超过了收益(TS带来的类型安全),TypeScript也学成了AnyScript

上述历程我反复经历了两次。痛定思痛,决定系统学一遍TS

经过这次系统学习,我终于明白我为什么总学不好TS。希望这篇文章对和我有同样经历的同学有帮助。

打包领取卡颂原创React教程、加入人类高质量前端群

学不好的原因

想必你听过一句话 ------ TS是JS的超集 。这句话本身是没错的,TSJS的基础上扩展了类型系统与语法。

但如果我们以这句话为基础开始学习TS,很容易形成一个惯性:以JS为起点,逐步学习TS知识。

也就是下图中从红圈(JS)逐渐向外学习(蓝圈),目标是最终覆盖绿圈(TS)。

从这个思路出发的学习步骤就是我们开篇提到的学习步骤。

按这个步骤学习的问题出在哪呢?当我们只把TS看作JS超集时,会忽略TS本身就是一门语言 这一事实。作为一门语言,TS有自己的语法规范,与JS相比:

  • TS作为语言,操作的单位是类型 ,语法规范定义的是类型之间的操作逻辑,工作在编译时

  • JS作为语言,操作的单位是变量 ,语法规范定义的是变量之间的操作逻辑,工作在运行时

如果我们只从JS出发,是可以理解TSJS兼容的部分(类型部分)。但不兼容的部分(TS作为语言本身的语法规则)会成为我们进阶路上的绊脚石。

一个例子

举个例子,下面三个都是TS中合法的类型:

  • object:对应引用类型

  • {}:空对象字面量对应的类型

  • ObjectObject构造函数对应的类型

请问下面三个类型别名的结果是什么?

extends关键字在条件类型语句 a extends b ? 中用于判断a是否是bb的子类型

ts 复制代码
type r0 = {} extends object ? true : false;
type r1 = object extends Object ? true : false;
type r2 = {} extends Object ? true : false;

即使没有TS经验,从JS语法出发,也能得到答案:

  1. {}是对象字面量,肯定属于对象类型的子类型,所以r0true

  2. Object处于JS原型链的顶端,所有对象类型肯定是他的子类型,所以r1true

  3. 有了前两个结果,r2显然也为true

为什么没有TS经验也能得出正确结果呢?因为TS在类型方面是兼容JS的。我们从JS角度出发就能得到正确的TS结果(注意上述r0~r2的结果都是编译时由TS计算出的)

但是,如果我们不学习TS作为语言本身的规则,理解下面代码时就会产生困惑(我们将上述三段代码中extends前后的类型调换下,得到的结果仍然都为true):

ts 复制代码
type r0 = object extends {} ? true : false;
type r1 = Object extends object ? true : false;
type r2 = Object extends {} ? true : false;

JS出发是很难理解这个结果的。要理解他,我们需要从TS出发。

类型与类型系统

JS中我们定义不同变量后,可以按照语法规则对变量进行不同操作:

js 复制代码
const num1 = 1;
const num2 = 2;
// 对变量的操作逻辑
console.log(num1 + num2); // 3

同样,在TS中,我们定义不同类型后,也能按照语法规则对类型进行不同操作:

ts 复制代码
type A = 1;
type B = 2;
// 对类型的操作逻辑
type C = A | B; // 1 | 2

TS的语法规则被称为结构化类型系统 ,与JS类比如下:

TS中,类型结构化类型系统 的关系可以用我们中学学到的集合的概念来类比,其中:

  • 类型 是一类值的集合,比如number是数字字面量的集合,interface A是满足接口A规范的对象的集合

  • 结构化类型系统 是集合之间兼容性判断的规则,比如怎么判断交集、怎么判断并集、怎么判断差集?

具体来讲,结构化类型 又叫鸭子类型,这是编程中一个很常见的术语,即 ------ 如果一只动物看起来像鸭子,叫起来像鸭子,走起来像鸭子,那他就是鸭子。

同样,结构化类型系统 在判断两个类型是否存在父子类型关系 时,也是通过对象成员是否有相同结构来判断的。

比如在下面代码中,我们定义CatDog类型,以及接收Cat类型参数的feedCat函数。在调用feedCat时,传入Dog的实例并不会报错:

ts 复制代码
class Cat {
    eat() {}
}

class Dog {
    eat() {}
}

function feedCat(cat: Cat) {}

feedCat(new Dog) // 不会报错

这是因为CatDog的成员结构一致(都只包括返回值一致的eat方法)。根据鸭子类型 ,既然成员结构一致,那CatDog就是同类,所以feedCat不会报错。

结构化类型系统 (鸭子类型)相对的是指称类型系统 。在指称类型系统 中,类名必须一致才会被判定为同类,类之间必须有明确的继承关系(extends)才会被判定为父子关系。

回到我们的代码:

ts 复制代码
type r0 = object extends {} ? true : false;
type r1 = Object extends object ? true : false;
type r2 = Object extends {} ? true : false;

{}代表一个没有任何成员的对象 。那么,换句话说,任何有成员的对象都能在{}的基础上延伸出来,比如下面的接口A可以看作是在{}的基础上增加了name属性:

ts 复制代码
interface A {name: string}

所以,根据结构化类型系统{}是任何对象的父类,所以r0r2Object是构造函数,函数也属于对象)为true

实际上,任何基础类型都有对应的包装类型,比如:

  • number对应Number

  • string对应String

  • boolean对应Boolean

包装类型都是对象,所以{}也是任何基础类型的父类(鸭子类型),即:

ts 复制代码
type r3 = 1 extends {} ? true : false; // true
type r4 = 'hello' extends {} ? true : false; // true
type r5 = true extends {} ? true : false; // true

对于r1,上面提到,Object是构造函数,函数也属于对象,所以是object的子类。

总结

TS的出现为JS带来静态分析能力。从这个角度看,TS是兼容JS的。所以从JS出发学习TS,在初期不会有很大阻力。

但是,TS本身也是一门语言,这门语言的操作对象是类型,语法规则叫结构化类型系统

所以,当我们想深入使用TS时,必然会触碰TS语言本身的规则,此时我们需要从TS出发学习。

只有这样,才能真的学懂、用好TS

最后推荐下林不渡的《TypeScript 全面进阶指南》小册,讲的通俗易懂,是不错的教程。

相关推荐
baiduopenmap12 分钟前
百度世界2024精选公开课:基于地图智能体的导航出行AI应用创新实践
前端·人工智能·百度地图
loooseFish19 分钟前
小程序webview我爱死你了 小程序webview和H5通讯
前端
请叫我欧皇i32 分钟前
html本地离线引入vant和vue2(详细步骤)
开发语言·前端·javascript
533_34 分钟前
[vue] 深拷贝 lodash cloneDeep
前端·javascript·vue.js
guokanglun40 分钟前
空间数据存储格式GeoJSON
前端
zhang-zan1 小时前
nodejs操作selenium-webdriver
前端·javascript·selenium
猫爪笔记1 小时前
前端:HTML (学习笔记)【2】
前端·笔记·学习·html
brief of gali1 小时前
记录一个奇怪的前端布局现象
前端
Json_181790144802 小时前
电商拍立淘按图搜索API接口系列,文档说明参考
前端·数据库
风尚云网3 小时前
风尚云网前端学习:一个简易前端新手友好的HTML5页面布局与样式设计
前端·css·学习·html·html5·风尚云网