TypeScript 学习笔记(十万字超详细知识点总结)
Posted 海底烧烤店ai
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了TypeScript 学习笔记(十万字超详细知识点总结)相关的知识,希望对你有一定的参考价值。
👉 订阅专栏学习TS不迷路:TypeScript从入门到精通
🖥️ 博主的前端之路(猿创征文一等奖作品):前端之行,任重道远(来自大三学长的万字自述)
🏆分享博主自用牛客网:一个非常全面的面试刷题求职网站,前端开发者必备的刷题网站,真的超级好用🍬
知识目录
一、介绍
1、javascript最大的问题
程序员编写的最常见的错误类型可以描述为类型错误:在预期不同类型的值的地方使用了某种类型的值。这可能是由于简单的拼写错误、无法理解库的 API 表面、对运行时行为的错误假设或其他错误。
使用JavaScript
编写代码最突出的问题就是类型检查问题:由于JavaScript
是弱类型语言,使得大多数使用者只能在代码运行阶段才能发现类型错误问题,这就使得错误不能被及时发现和修复,为之后的开发埋下了隐患。
JavaScript
没有表达不同代码单元之间关系的能力。结合 JavaScript
相当奇特的运行时语义,语言和程序复杂性之间的这种不匹配使得 JavaScript
开发成为一项难以大规模管理的任务。
TypeScript
的目标是成为 JavaScript
程序的静态类型检查器——换句话说,是一个在代码运行之前运行的工具(静态)并确保程序的类型正确(类型检查),使得我们能够在代码编写阶段就能及时发现类型错误问题。
2、什么是TypeScript
TypeScript
是一种由微软开发的自由和开源的编程语言。它是 JavaScript
的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。
TypeScript
是一种非常受欢迎的 JavaScript
语言扩展。它在现有的 JavaScript
语法之上加入了一层类型层,而这一层即使被删除,也丝毫不会影响运行时的原有表现。许多人认为 TypeScript
“只是一个编译器”,但更好的理解其实是把 TypeScript
看作两个独立的系统:编译器(即处理语法的部分)和语言工具(即处理与编辑器集成的部分)。
3、JS , ES , TS 的关系
-
1995年:JavaScript诞生
当时的网景公司正凭借其
Navigator
浏览器成为Web
时代开启时最著名的第一代互联网公司。由于网景公司希望能在静态
html
页面上添加一些动态效果,于是 Brendan Eich 在两周之内设计出了
JavaScript
语言。之所以起名叫
JavaScript
,是原因是当时Java
语言非常红火,想要蹭一波热度而已,实际上JavaScript
除了语法上有点像Java
,其他部分基本上没啥关系。 -
1997年:ECMAScript诞生
因为网景开发了
JavaScript
,一年后微软又模仿JavaScript
开发了JScript
,为了让JavaScript
成为全球标准,几个公司联合ECMA
(European Computer Manufacturers Association)(欧洲计算机制造商协会)组织制定了JavaScript
语言的标准,被称为ECMAScript
标准。 -
2015年:TypeScript诞生
TypeScript
是JavaScript
的超集(最终会被编译成JavaScript
代码),即包含JavaScript
的所有元素,能运行JavaScript
的代码,并扩展了JavaScript
的语法。相比于JavaScript
,它还增加了静态类型、类、模块、接口和类型注解方面的功能,更易于大项目的开发。TypeScript
提供最新的和不断发展的JavaScript
特性,包括那些来自 2015 年的ECMAScript
和未来的提案中的特性,比如异步功能和Decorators
,以帮助建立健壮的组件。
一句话总结三者关系:ECMAScript
是标准语言,JavaScript
是ECMAScript
的实现,TypeScript
是JavaScript
的超集。
4、为什么使用TypeScript
TypeScript
扩展了JavaScript
,提供强大的类型检查和语法提示功能,结合诸如VS code
这类编译器,能够极大的提高开发效率,降低项目后期的维护成本:
5、配置TypeScript环境
在学习TypeScript
之前我们需要先全局安装tsc
TypeScript
编译器。
npm i -g typescript
自己创建一个项目(文件夹),在项目根目录终端运行:
tsc -init
此时项目根目录下会生成一个配置文件 tsconfig.json
,这里给出我使用的配置:
"compilerOptions":
/* TS编译成JS的版本*/
"target": "es6",
/* 入口文件:指定源文件中的根文件夹。 */
"rootDir": "./src",
/* 编译出口文件:指定输出文件夹。 */
"outDir": "./dist",
/* 严格模式 */
"strict": true,
项目根目录下创建src
文件夹,在这个文件内创建并编写你的ts
文件
在项目根目录终端输入tsc --watch
就会开始持续监听src
目录下的所有ts
文件,文件更改时会自动将其编译成js
文件到dist
目录下
如果想要自己手动编译某一特定ts
文件,可以在ts
文件所在目录下运行tsc xxx.ts
,这时编译后的js
文件会放在与该ts
文件同级的地方
注意:我们使用
TypeScript
的目的就是检验代码并纠错,尽量使自己的代码变得足够规范,所以建议应始终使用"strict": true
二、数据类型
1、基元类型
JavaScript
有三个非常常用的类型: string
, number
,和boolean
。
它们在 TypeScript
中都有对应的类型,并且这些名称与在 JavaScript
应用typeof
返回的类型的名称相同:
string
表示字符串值,如"Hello, world"
number
表示数字值,如 42 。JavaScript
没有一个特殊的整数运行时值,所以没有等价于int
或float
类型, 一切都只是number
boolean
只有两个值true
和false
类型名称
String
,Number
, 和Boolean
(以大写字母开头)是合法的,但指的是一些很少出现在
代码中的特殊内置类型。对于类型,始终使用string
,number
, 或boolean
。
在TypeScript
中,当你使用const
, var
, 或let
时可以直接在变量后加上类型注释: type
就可以显式指定变量的类型为type
:
var str: string = "hello,world";
const num: number = 42;
let boo: boolean = true;
但是,在大多数情况下,这不是必需的。只要有可能,TypeScript
就会尝试自动推断代码中的类型。例如,变量的类型是根据其初始化器的类型推断出来的:
// 不需要类型定义--“myName”自动推断为类型“string”
let myName = "AiLjx";
对于已经声明类型的变量,对其赋值时只能赋予其相同类型的数据,否者TypeScript
将会抛出错误:
2、数组
指定数组的类型可以使用ItemType[]
或者Array<ItemType>
,ItemType
指数组元素的类型,
Array<ItemType>
声明类型的方式使用了TypeScript
的泛型语法,对于泛型,之后我会出单独的一篇博文对其详细的介绍
const arr1:number[]=[1,2,3]
const arr2:string[]=['1','2','3']
同样的,对已经指定类型的数组赋不同类型的值,或向其添加不同类型的数据时,TypeScript将会抛出错误:
3、any
TypeScript
还有一个特殊类型 any
,当你不希望某个特定值导致类型检查错误时,可以使用它。
当一个值的类型是any
时,可以访问它的任何属性,将它分配给任何类型的值,或者几乎任何其他语法上的东西都合法的:
src/01-type.ts
:
let obj: any = x: 0 ;
// 以下代码行都不会抛出编译器错误。
// 使用'any'将禁用所有进一步的类型检查
obj.foo();
obj();
obj.bar = 100;
obj = "hello";
const n: number = obj;
但在运行环境下执行代码可能是错误的:
在项目根目录下(
tsconfig.json
所在路径)通过运行tsc --watch
(此命令执行一次后会持续监听入口目录下的文件,其发生变化时会自动重新编译)由typescript
包编译src
目录下的文件,编译后的文件就在dist
目录下(前提是tsconfig.json
中配置了"outDir": "./dist"
):
进入到 dist
目录中,在 node
环境里运行代码,果然报错了。
当你不想写出长类型只是为了让 TypeScript
相信特定的代码行没问题时, any
类型很有用。
但万万不可大量使用any
类型,因为any
没有进行类型检查,使用它就相当于在使用原生JS
,失去了TS
的意义!!!
4、函数
TypeScript
允许您指定函数的输入和输出值的类型。
- 参数类型注释
声明函数时,可以在每个参数后添加类型注解,以声明函数接受的参数类型。参数类型注释位于参数名称之后:
// 参数类型定义
function getName(name: string)
console.log("Hello, " + name);
这里指定了getName
函数接收一个string
类型的参数,当参数类型不匹配时将抛出错误:
即使您的参数上没有类型注释,
TypeScript
仍会检查您是否传递了正确数量的参数!
- 返回类型注释
返回类型注释出现在参数列表之后,其指定了函数返回值的类型:
function getName(name: string): string
console.log("Hello, " + name);
return "Hello, " + name;
这里对getName
函数指定了其返回值是string
类型,当函数无返回值或返回值不是string
类型时将抛出错误:
与变量类型注释非常相似,通常不需要返回类型注释,因为
TypeScript
会根据其return
语句推断函数的返回类型。上面例子中的类型注释不会改变任何东西。某些代码库会出于文档目的明确指定返回类型,以防止意外更改或仅出于个人偏好。
- 匿名函数
匿名函数与函数声明有点不同。当一个函数出现在 TypeScript
可以确定它将如何被调用的地方时,该函数的参数会自动指定类型。
即使参数s
没有类型注释,TypeScript
也会使用forEach
函数的类型,以及数组的推断类型来确定s
的类型。这个过程称为上下文类型,因为函数发生在其中的上下文通知它应该具有什么类型。
与推理规则类似,你不需要明确了解这是如何发生的,但了解它的机制确实可以帮助你注意何时不需要类型注释。
从这里我们就能看出TypeScript
的强大之处,它不仅能够自动推断类型并发现错误,还能提示你错误的地方,以及修复建议,配合VS code
编译器还能实现快速修复:
5、对象
除了string
, number
, boolean
类型(又称基元类型)外,你将遇到的最常见的类型是对象类型。
这指的是任何带有属性的 JavaScript
值,几乎是所有属性!要定义对象类型,我们只需列出其属性及其类型。
let obj: x: number; y: number = x: 1, y: 2 ;
对于指定类型的对象其值不符合指定的类型时抛出错误:
- 可选属性
在指定的类型属性名后加上一个?
,可以指定该属性为可选属性:
let obj2: x?: number; y: number =
y: 2, // x 是可选属性,对象内不含x属性时将不再抛出错误
;
不能直接对可选属性进行操作,不然就会抛出错误:
这很好理解,因为可选属性没有限制用户必传,如果访问一个不存在的属性,将获得值undefined
,此时对其操作TypeScript
就会抛出错误提醒你。
正确的做法:
function ObjFn(obj: x?: number; y: number )
console.log(obj.y++);
if (obj.x) // 先判断可选属性是否存在
console.log(obj.x++);
6、unknown
与 any
类型类似,可以设置任何的类型值,随后可以更改类型,但unknown
要比any
更加安全,看个例子:
let a: any = "Ailjx";
a = [];
a.push("0");
上面代码在编译与运行时都是正常的,但是当我们手误写错了push
方法后你就会发现问题所在:
let a: any = "Ailjx";
a = [];
a.psh("0");
这段代码在编译时不会报错,只会在运行时报错,这就失去了TypeScript
在编译时检查错误的功能,在项目比较大时,参与的人多时,就很难避免这样类似的问题,因此unknown
类型出现了:
虽然我们将其类型更改为数组类型,但是编译器认为其依旧是unknown
类型,该类型没有push
方法,就会报错,除非我们先判断类型:
let a: unknown = "Ailjx";
a = [];
if (a instanceof Array)
a.push("0");
这样代码就没问题了,这时如果你push方法写错了,编译器就会报错提示你了:
虽然有些麻烦,但是相比 any
类型说,更加安全,在代码编译期间,就能帮我们发现由于类型造成的问题,因此在大多的场景,建议使用 unknown
类型替代 any
。
7、其它类型
void
void
表示不返回值的函数的返回值:
function A()
const a = A(); // type A = void
只要函数没有任何
return
语句,或者没有从这些返回语句中返回任何显式值,它的推断类型就是void
在
JavaScript
中,一个不返回任何值的函数将隐含地返回undefinded
的值,但是,在TypeScript
中,void
和undefined
是不一样的
object
特殊类型 object
指的是任何不是基元的值( string
、number
、bigint
、boolean
、symbol
、null
或 undefined
)(即对象)
这与空对象类型 不同,也与全局类型
Object
(大写的O
)不同, Object
类型一般永远也用不上,使用的都是object
let a: object; // a只能接受一个对象
a = ;
a =
name: "Ailjx",
;
a = function () ;
a = 1; // err:不能将类型“number”分配给类型“object”
请注意,在
JavaScript
中,函数值是对象,它们有属性,在它们的原型链中有Object.prototype
,是Object
的实例,你可以对它们调用Object.key
等等,由于这个原因,函数类型在TypeScript
中被认为是object
!
never
never
类型表示的是那些永不存在的值的类型:
-
可以表示总是抛出异常或根本不会有返回值的函数的返回值类型
function error(msg: string): never throw new Error(msg); // 推断出fail返回值类型为never function fail() return error("Ailjx"); // A函数会造成死循环,根本不会有返回值,可以用never来表示返回值类型 function A(): never while (true)
-
被永不为真的类型保护所约束下的变量类型
function Sw(a: boolean) switch (a) case true: return a; case false: return a; default: // 这个分支永远不可能到达 // 此时 _a类型为 never const _a = a; return _a;
-
never
类型可以分配给每个类型,但是,没有任何类型可以分配给never
(除了never
本身)
never
类型在实际开发中几乎是使用不到,最大的用处可能就是用来表达一个总是抛出异常的函数的返回值类型了
Function
全局类型Function
描述了 JavaScript
中所有函数值上的属性,如bind
、call
、apply
和其他属性,即 Function
类型的值可以被任何函数赋值,并且总是可以被调用(不会受参数的限制),这些调用返回的都是 any
类型
let fn: Function;
fn = () => ;
fn = function () ;
fn = function (a: number): number
return a;
;
// 虽然fn的值是一个必须接受一个number类型参数的函数
// 但因为fn类型为Function,调用fn时可以不传参数
fn();
fn('1',2,true) // 还可以随便传参
const a = fn(1); // a的类型依旧为any
从上面调用fn
的例子可以知道这并不安全,一般来说最好避免,因为 any
返回类型都不安全,并且也失去了参数的类型限制
一般情况下,想表示一个函数几乎不会使用Function
,而是使用函数类型
8、联合类型
- 定义联合类型
联合类型是由两个或多个其他类型组成的类型,表示可能是这些类型中的任何一种的值。我们将这些类型中的每一种称为联合类型的成员。
多个类型之间使用|
分割:
function getId(id: string | number)
console.log("id=", id);
getId(1);
getId("1");
这个例子中getId
接收的参数id
可为string
类型也可为number
类型,当类型不匹配时依旧会抛出错误:
- 使用联合类型
在使用联合类型时需要注意的是:不能盲目将联合类型的数据当成单独类型的数据进行操作,不然TypeScript
将抛出错误提醒你:
这里直接对联合类型id
进行字符串上的toUpperCase
操作,TypeScript
会自动检测id
联合类型的成员是否都具有toUpperCase
属性,这里检测到联合类型的成员number
类型并不具有toUpperCase
属性,所以会抛出错误提示用户。
正确的做法是:
function getId(id: string | number)
if (typeof id === "string")
// 在此分支中,TS自动检测id的类型为“string”
console.log(id.toUpperCase());
else
// 此处,TS自动检测id的类型为“number”
console.log(id.toString());
先使用判断语句确定id
具体的类型,再对其进行操作(这称为类型缩小,博主后期会出另外的博文对其详细介绍),TypeScript
会非常智能的检测判断分支的语句中id
的类型。
9、类型别名
前面我们声明类型都是直接在类型注释中编写类型来使用它们,这很方便,但是想要多次使用同一个类型,并用一个名称来引用它是很常见的。
这就可以使用类型别名type
来声明类型:
type Id = number | string;
// 在类型注释中直接使用类型别名
function getId(id: Id)
console.log("id=", id);
getId(1);
getId("1");
在定义类型别名以及后面讲到的接口时,都建议将首字母进行大写,如上面例子中的Id
- 类型别名也可以声明复杂的类型:
type Point =
x: number;
y: number;
;
function printCoord(pt: Point)
console.log("坐标x的值是: " + pt.x);
console.log("坐标y的值是: " + pt.y);
printCoord( x: 100, y: 100 );
- 扩展类型(交叉类型)
类型别名可以使用交叉点&
来扩展类型:
type User =
name: string;
;
type Admin = User &
isAdmin: boolean;
;
const admin: Admin =
name: "Ailjx",
isAdmin: true,
;
这里Admin
在User
基础上扩展了isAdmin
类型,当使用Admin
并赋予的类型不匹配时将抛出错误:
注意这里报的错,在下面的接口与类型别名的区别中会详细分析这个报错。
梳理一下,之所以称其为类型别名,就是因为它只是用一个名称来指向一种类型,当用户需要使用该种类型时可直接使用该名称,方便复用,也方便将类型与业务代码抽离开来。
10、接口
一个接口声明interface
是另一种方式来命名对象类型:
interface Point
x: number;
y: number;
// 与前面的示例完全相同
function printCoord(pt: Point)
console.log("坐标x的值是: " + pt.x);
console.log("坐标y的值是: " + pt.y);
printCoord( x: 100, y: 100 );
- 类型别名和接口之间的差异
类型别名和接口非常相似,在很多情况下你
以上是关于TypeScript 学习笔记(十万字超详细知识点总结)的主要内容,如果未能解决你的问题,请参考以下文章
JavaScript手撕前端面试题:事件委托 | 判断URL是否合法 | 全排列
JavaScript手撕前端面试题:手写new操作符 | 手写Object.freeze
JavaScript手撕前端面试题:手写Object.create | 手写Function.call | 手写Function.bind