后端小白的TypeScript入门学习笔记
写在前面
本博文仅作为个人学习过程的记录,可能存在诸多错误,希望各位看官不吝赐教,支持错误所在,帮助小白成长!
一、TypeScript入门了解
1.1、什么是TypeScript

学习TypeScript前,我听过的关于它最多的一句话就是:TypeScript是JavaScript的超集!
TypeScript在JavaScript的基础与ES标准之上,加上了接口、类型限定、泛型等功能,强化了代码静态检查。更易于我们编写代码,但是TypeScript语法的代码目前无法直接运行在浏览器上,需要将TypeScript代码通过编译器(tsc)转化为JavaScript代码然后再进行使用!(TypeScript中可以使用所有JavaScript语法)
TypeScript由微软开发并开源,在2012.10发布了TypeScript第一个公开版本,正式版于2013.6.19发行!TypeScript的作者是***安德斯·海尔斯伯格***,C#的首席架构师。
1.2、快速上手
安装TypeScript
安装TypeScript:
npm install -g typescript
验证安装:(当前笔记使用版本为4.3.2)
tsc -V
代码文件编写
创建一个文件夹作为代码目录:
- 创建index.ts文件
- 创建index.html文件
index.ts
function sayHi(speaker: string) {
console.log(`${speaker}: Hello TypeScript!`)
}
sayHi('sakura');
这里我们使用了一个ts的语法:
speaker: string
,对函数参数类型做了限定!此时如果你在调用sayHi
函数时,不指定参数或者参数类型不是string时,就会报错!(但是编译依然会通过!)
如果此时我们直接在html中引入ts代码文件,并在浏览器中运行,浏览器是会报错的!因为含有ts语法代码是无法直接在浏览器中运行的!(如果没有使用ts语法,只是基础的js代码,依然可以使用)此时需要对ts代码进行编译!
编译(ts -> js)
我们安装TypeScript时,同时为我们安装了TypeScript代码的编译器tsc,使之转化为JavaScript代码。
执行以下命令,将ts代码编译成js代码
tsc ./index.ts
然后命令执行所在目录就会生成编译后的js代码,然后我们在Html文件中进行引入即可!
1.3、TypeScript自动编译(vscode)
首先在代码目录中执行命令:
tsc init
会生成一个tsconfig.json
文件,文化中包含了一些编译配置选项,其中有一个outDir
默认值是./
即编译后的文件是在命令执行的所在目录。你可以按需修改为其他位置,这里我修改为./js/
。
然后我们使用ctrl+shift+b
,打开task运行面板,选中tsc watch
(如果有多个项目目录,注意匹配当前代码目录的配置文件!)
如果修改了快捷键,可以在【菜单栏】-> 【Terminal】-> 【Run Task …】-> 【typescript】中找到任务并执行。
此时tsc会在后台监控目录下的ts文件,如果发生了变化将实时对其编译!
1.4、搭建Webpack项目
环境准备
当项目工程化以后,我们如何在webpack中配置TypeScript自动编译?!
首先我们使用npm init -y
对工程进行初始化(如果你全局安装了webpack和webpack-cli,可以省略此步骤)
然后执行npx webpack init
npx会优先使用项目本地依赖包中的webpack,并且会临时安装可执行依赖。
如果全局安装直接使用webpack init
然后开始进行webpack项目配置:
- 选用
typescript
- 启用
webpack-dev-server
- 启用简化HTML文件创建和bundle引入
- 样式文件如果没有选择
none
- 不启用
Prettier
- 最后选择覆写package.json
然后静静等待配置文件生成即可!
基础测试
完成后,会发现我们ts转换js的ts-loader已经帮我们安装了,npm脚本命令也已经为我们配置好了,HttpWebpackPlugin也已经配置好了。


如果还需要其他配置,可以在webpack.config.js
中进行修改!例如在output
中设置clean:true
,开启dist文件夹清理【对webpack不熟悉的,复习一下webpack,再按需对配置文件进行修改~】
你先你可以先尝试在src/index.ts中写一些ts语法的代码,然后命令行中执行npm run build:dev
(开发环境打包),然后dist目录中就是打包构建的内容了,ts代码也被成功编译为js代码,并自动引入了!
现在你可以使用npm run serve
启动webpack-dev-server,启动后我们可以实现代码的热部署,每次我们代码变化后,都会自动进行build,然后我们在浏览器上刷新就可以看到最新的代码效果了!!
二、TypeScript特性
2.1、基础类型
TypeScript相较于JavaScript变化最明显的,最容易感受到的就是加上了类型限定,通过x:number
此类形式为变量/属性设置类型,一旦声明时类型确定就不再改变,妥妥的静态类型语言!这也就是为什么我们在编写JavaScript代码时针对类型的错误,我们不便于排查,但是换上TypeScript,就像写Java一样,代码逻辑上一点纰漏都会被排查出来!
number、string、boolean
以上几种类型,但凡学过任何一门语言应该都知道,直接略过!
说明一点:在JavaScript和TypeScript中,所有的数字都是浮点数,所有没有整型和浮点型只分,同属于number!TypeScript支持二、八、十、十六进制字面量:
let decLiteral: number = 6;
let hexLiteral: number = 0xf00d; // 十六进制
let binaryLiteral: number = 0b1010; // 二进制
let octalLiteral: number = 0o744; // 八进制
Array、Tuple
数组应该都很熟悉,直接上代码:
// 声明数组
let numbers: number[] = [1, 3, 5, 7, 9]
console.log(numbers[2]); // 5
// 使用Array+泛型声明数组
let numbers2: Array<number> = [2, 4, 6, 8, 0]
console.log(numbers2[2]); // 6
在TypeScript中,当你为数组元素类型做了限定后(即使用了arr: number[]
或arr: Array<number>
)数组中只能放置对应类型的数据。
在JavaScript中,数组没有元素类型的限定,多种类型的元素允许出现在一个数组中:
let arr = [1, 'Hello', true, 3.1415926]
当然这种操作也是可以用TS语法实现的,不过需要一个特殊的类型Any
,这个后面再说。
Tuple元组,可以将看做是一个指定大小,并且指定每个位置元素类型的数组。在声明时就确定了元组的大小以及各个位置元素的类型:
let tuple: [number, string, boolean] = [123123, 'sakura', true]
console.log(`id :${tuple[0]} | name: ${tuple[1]} | isMan: ${tuple[2]}`);
使用元组,我们可以将看似无关数据进行组合,形成一个整体!
enum
年少不知枚举好,错把if-else当成宝!
使用枚举,我们可以为复杂的数据取名字,方便我们在使用时进行调用。
创建一个枚举类型:
enum Color {
red,
blue,
green
}
因为此时,我们还没有为枚举中的元素进行赋值,所以元素会默认从0开始编号,将其编号作为自己的元素值:
console.log(Color.red); // 0
console.log(Color.blue); // 1
console.log(Color.green); // 2
当你某个元素赋number类型值后,后面的元素会重新以此数值为起点重新计算编号:
enum Color {
red,
blue = 99,
green
}
console.log(Color.red); // 0
console.log(Color.blue); // 99
console.log(Color.green); // 100,从99开始计算编号
但是如果你为元素赋值为字符串类型,那么其后的元素都必须进行赋值!(只有数字类型的枚举可以计算元素值!并且只能使用字面量进行赋值。)
但我们是希望用Color.red
来代替一个复杂的数据,我们可以给每个元素都指定数据值:
enum Color {
red = 0xff0000,
blue = 0x0000ff,
green = 0x00ff00
}
更厉害的是,你可以使用元素值反过来去取出泛型元素名:
console.log(Color[0xff0000]) // red
undefined、null、void
在TypeScript中,undefined和null,都有他们各自的类型:undefined
、null
,很少会将他们设置为数据类型,在严格模式下,undefined和null只能赋值给他们自己本身或者void
let data: undefined = undefined;
data = null; // 错误!
let data2: null = undefined; // 错误!
data2 = null;
let data3: void = undefined;
data3 = null // 错误!
let data4: number = undefined; // 错误!
当你关闭了严格模式后,null、undefined可以作为任何类型的子类,那么以上代码中的写法就都可以通过检查!
ts开启严格模式
tsconfig.json文件中修改“compilerOptions.strict”选项值
{ "compilerOptions": { "allowSyntheticDefaultImports": true, "noImplicitAny": true, "module": "es6", "target": "es5", "allowJs": true, "strict": true // true开启、false关闭 }, "files": ["src/index.ts"] }
void类型,表示没有任何类型!通常用于函数返回值类型的设定!
any
与void表示相反,any表示任何类型。通常用于类型不确定的变量、参数设置。
例如上面说的实现多类型元素出现在同一数组:
let arr:any[] = [1, 'Hello World', true, undefined]
// 或者
let arr:Array<any> = [1, 'Hello World', true, undefined]
当我们声明了一个any类型的变量后,任何类型的值都可以直接赋给此变量
let variable: any
variable = 1
variable = 'Hello'
variable = true
variable = undefined
使用了any后,写ts代码就和js代码一样的,没有类型限制。但是会牺牲TypeScript的静态类型检查功能!
object
object表示非基本类型(即除number、string、boolean、undefined/null【仅限严格模式下,非严格模式下undefined和null可以赋值给object类型变量】)
let obj: object = 1 // error
obj = 'Hello' // error
obj = undefined // 严格模式下 error
obj = null // 严格模式下 error
obj = {name: 'sakura', age: 20} // bingo!
2.2、高级类型
联合类型
当函数的参数类型不确定时,但是你已知参数的类型范围时,就可以使用联合类型。arg: A|B
它表示参数可能为类型A或者类型B。如果这里使用any,那么就失去了类型限定的意义,我们希望调用者传入的参数是我们预期之内的!
function returnNumber(arg: number | string): number {
if (typeof arg === 'number') {
return arg
} else {
return (<string>arg).length
}
}
console.log(returnNumber(99)) // 99
console.log(returnNumber('Hello World')) // 11
类型断言
上面联合类型中,我们使用一段代码:(<string>arg).length
。可能感觉到是在将参数转换为string类型。我们将这种转换方式称为类型断言。意在告诉编译器,”我比你更懂这个数据现在是什么类型“。你帮我装换成对应的类型就行。
除了使用尖括号写法,还可以使用关键字as
进行转换:
(<string>arg).length
// 或者
(arg as string).length
这两个东西,后面我们还会遇到,以后慢慢说。
接口
在Java中,接口可以视为是对象属性、方法的抽象。或者我们可以将它看做为约束、规范、能力。它甚至可以当做类型来使用!
基本使用
使用关键字interface即可创建一个接口。
interface ISpeak {
name: string
speak: () => void // 函数类型,请查看函数笔记
}
接口旨在对功能进行扩展,你可以使用implements
实现接口,扩展类的功能。你也可以将接口当作类型使用:
let dog: ISpeak = {
name: '旺财',
speak: () => {
console.log(`汪汪汪`)
}
}
dog.speak() // 汪汪汪
let dog2 = {
name: '阿呆',
speak: () => {console.log(`嗷嗷嗷?`)}
}
dog = dog2
dog.speak() // 嗷嗷嗷?
只要对象的结构满足接口的结构(属性对应,不多不少!),就可以视为是此接口类型的对象!
接口是可以使用extends
进行继承的,这点和Java也一样。
可选属性
当接口中有些属性,不是强制需要时,可以使用?
修饰将其设置为可选属性:
interface ISpeak {
name: string
age?: number // 可选属性
speak: () => void
}
let student:ISpeak = {
name: 'sakura',
age: 20,
speak: () => { console.log('我要学习!!') }
}
// 不想透露年龄
let stranger:ISpeak = {
name: 'Philip',
speak: () => { console.log('我很忙!!') }
}
只读属性
当接口中的属性不想在外部进行修改时,我需要将其使用readonly
将其设置为只读。(而不是const!!const只能用于修饰变量!属性修饰需要使用readonly!)
interface ISpeak {
readonly name: string
age?: number
speak: () => void
}
let student:ISpeak = {
name: 'sakura',
age: 20,
speak: () => { console.log('我要学习!!') }
}
student.name = 'Philip' // ERROR
注意一定要声明类型为ISpeak,如果只是结构相同,并不会生效:
let stranger = {
name: 'xiaoming',
age: 18,
speak: () => {
// ...
}
}
stranger.name = 'xiaogang' // 不会报错,因为没有显示说明变量类型为ISpeak, 无法进行检查!
函数类型
除了在接口中定义属性外,还可以对行为进行抽象,也就是本节要提到的函数类型。使用一个函数类型描述一个行为。
例如上述代码中的speak: () => void
,表示一个speak是一个行为抽象,无参数,返回值是void。还可以写成speak(): void;
,当接口中只有一个函数时,甚至可以省去函数名。写成(): void
。
__后续学习泛型以后,函数类型的描述将更加多样!__例如func: <T, K>(arg1:T, arg2:K) => T
类
在JavaScript中我们已经广泛使用类来完成面向对象编程,在TypeScript中类的特性并没有改变。我们还是可以像过去一样使用类。不过TypeScript中多了接口类型后,便于我们使用接口来扩展类的功能。
interface
interface IFly {
fly(): void
}
class
class Animal {
name: string
constructor(name: string) {
this.name = name
}
}
class Cuckoo extends Animal implements IFly {
constructor(name: string) {
super(name)
}
fly(): void {
console.log("起飞,布谷布谷")
}
shout() {
console.log("我是一只布谷鸟,布谷布谷")
}
}
强制:子类的构造函数中一定要显式调用父类的构造函数!!
构造函数
在定义类的构造函数时,我们可以为某些参数指定默认值!
class Cuckoo extends Animal implements IFly {
constructor(name: string = "布谷鸟") { // 为name设置默认值
super(name)
}
fly(): void {
console.log(this.name + ": 起飞,布谷布谷")
}
shout() {
console.log(this.name + "我是一只布谷鸟,布谷布谷")
}
}
new Cuckoo('库库').fly() // 库库:起飞,...
new Cuckoo().fly() // 布谷鸟: 起飞,布谷布谷
你也可以为成员属性赋默认初始值:(接口中不适用!!)
class Animal {
name: string = "animal" // 属性初始值
constructor(name: string) {
this.name = name
}
}
你还可以将类属性的声明放到构造函数的参数中完成:
class Animal {
constructor(readonly name: string = "animal", private id: number) {
this.name = name
}
}
访问权限修饰符
private
、protected
、public(默认)
,在Java中我们已经学习过他们三者的对访问范围的界定了。稍微有点区别的是Java中默认的访问修饰符不是public。
public是公有的访问修饰符,我们就不过多说明了。
private
私有访问修饰符,修饰的属性和方法只能在类中进行使用,我就不就行验证了。一个关于TypeScript语言特点的问题需要说明一下:
TypeScript使用的是结构性类型系统。 当我们比较两种不同的类型时,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。
class Cat {
public name: string
public constructor(name: string = "Tom") {
this.name = name
}
}
class Dog {
public name: string
public constructor(name: string = "Bob") {
this.name = name
}
}
let dog: Dog = new Dog()
let cat: Cat = new Cat()
dog = cat // what?!
上面代码中你感到疑惑的点就是:明明是两个类,并且没有任何的继承关系,但是居然可以互相引用(Cat引用居然可以接受一个Dog实例)!?这就是上面所说的结构性类型系统!你是什么类型我并不关心,只要结构相同我认为你们可以相互兼容!(这里的兼容,指的是可以相互交换引用,并不是说两个类无差别!)
但是这个规则只适用于类中没有使用private
和protected
访问修饰符!
当使用了private、protected访问修饰符后,在结构相同的情况下,两个对象的private、protected成员属性必须来自于同一处声明!才能认为两个类型是相互兼容的!
什么叫做来自于同一处声明呢?!
打个比方:现在有一个Animal类,其有一个private属性name。然后又创建一个Cat类,并且extends Animal。那么当你创建的Animal对象和Cat对象,他们的name属性就来自于同一处声明。(即Animal类中private name:string
)
相反,如果你创建了一个Person类,没有继承Animal类,并且声明了一个name属性,那么创建的Person对象中的name和Animal、Cat对象的name就不是来自于同一处声明,固然Person与Animal、Cat不是类型兼容的!
class Animal {
private name: string
constructor(name: string = "Animal") {
this.name = name
}
}
class Cat extends Animal {
// 从父类Animal继承name属性
public constructor(name: string = "Tom") {
super(name)
}
}
class Person {
private name: string
constructor(name: string = "Fitz") {
this.name = name
}
}
let animal: Animal = new Animal()
let cat: Cat = new Cat()
let person: Person = new Person()
cat = animal // bingo
/*
cat = person // error
animal = person // error
person = cat // error
*/
protected
相较于private更宽松一点,protected修饰的属性/方法可以在其派生类中进行使用!
只读属性
和接口中声明只读属性一样使用readonly
修饰符,结合属性默认值使用效果更佳!
存取器
在Java中我们使用getter/setter来对属性值进行修改和访问!在TypeScript中一样也可以,不过有些特别!
我们设置存取器的时候,就是希望外部只能通过我们预留的get/set方法来对属性进行访问与修改,而不是随意的使用对象直接通过属性名进行访问到属性并进行修改。在TypeScript中预留了两个关键字get
、set
我们现在就来看看如何在ts中写getter、setter:
class Person {
private _id: string
constructor(id: string) {
this._id = id
}
get id(): string {
return this._id
}
set id(value: string) {
this._id = value
}
}
let sakura = new Person('420xxx20000817xxxx')
console.log(sakura.id);
sakura.id = '444xxx20000718xxxx'
console.log(sakura.id)
你会发现get、set调用并不像我在Java中那样直接用方法完成,而是通过属性名。此时如果你不知道类的结构时,你丝毫感觉不到get、set方法的存在,一个private属性,如果我想让你在外部访问或者修改,我就可以设置一个get/set方法。
如果一个private属性只设置了get,那么就意味着它是一个只读的属性!!
静态属性
熟悉Java语言的同学,应该不陌生。静态的属性与方法并不属于某一个实例,而是属于类的!所有实例都共用!使用static
修饰即表明属性、方法是静态的
抽象类
对类结构的一种抽象描述,不可进行实例化,声明时使用abstract class
。如果说接口的作用是功能扩展,那么抽象类作用就是对事物进行抽象,由其他类进行继承完善细节。
函数
和JavaScript中的函数使用大体相同,一样有闭包概念。我们用一个简单的例子快速回忆JavaScript中的函数:
// 匿名函数
let addFunc = function(x, y) {
return x + y
}
在TypeScript中加入类型限定后,我们可以通过表达式来表示一个函数类型:
let addFunc:(x:number, y:number) => number
function add(base:number, incr:number) {
return base + incr;
}
addFunc = add
console.log(addFunc(2, 4)) // 6
可选参数、参数默认值
可选参数必须跟在必要参数后!
function incr(base: number, step?: number): number {
if (step !== undefined) {
return base + step;
}
return base + 1;
}
console.log(incr(3, 3)) // 6
console.log(incr(3)) // 4
带默认值的参数,当函数调用时传入参数为undefined(或者未传入时)时,如果设置了参数默认值会使用参数默认值!
function incr2(base: number, step: number = 1): number {
return base + step;
}
console.log(incr2(4, 4)); // 8
console.log(incr2(4)); // 5
console.log(incr2(4, undefined)); // 5
当带默认值的参数写在前面时,如果想使用参数默认值就只能传undefined
!否则会影响后面的参数匹配!
默认值参数和可选参数不能同时出现在一个参数身上!
x?:number = 0
❌
剩余参数
剩余参数只能写在函数参数的末尾!标志符是...
,并且类型一般都是数组!!
function printNumbers(arg1: number, arg2: number, ...rest: number[]): void {
console.log(arg1);
console.log(arg2);
rest.forEach((value) => {console.log(value)});
}
printNumbers(1, 2, 3, 4, 5, 6, 7, 8, 9)
函数重载
在Java中,我们进行函数重载,都是进行额外的实现。每个重载的函数都会有自己的功能实现代码,即使只是调用了其他方法。但是在TypeScript中,函数的重载是通过定义重载完成,然后借助联合类型。
function compare(arg1: number, arg2: number): void
function compare(arg1: string, arg2: string): void
function compare(arg1: number | string, arg2: number | string): void {
if (typeof arg1 == 'number' && typeof arg2 == 'number') {
if (arg1 > arg2) {
console.log(`${arg1} > ${arg2}`)
} else {
console.log(`${arg1} <= ${arg2}`)
}
}
if (typeof arg1 == 'string' && typeof arg2 == 'string') {
if ((arg1 as string).length > (arg2 as string).length) {
console.log(`${arg1} > ${arg2}`)
} else {
console.log(`${arg1} <= ${arg2}`)
}
}
}
compare('Hello', 'TypeScript') // Hello <= TypeScript
compare(10, 11) // 10 <= 11
重载函数的实现并不参与类型检查,只有重载定义的函数才会被检查!!
compare(10, 'Hello') // ERROR
compare('Hola', 10) // ERROR
泛型
在Java中我们使用泛型,能够让方法针对多种类型的参数值都可以应对!TypeScript也是如此,使用泛型后一套代码逻辑可以在多种类型的参数下进行利用。大大提供了代码的复用率!
在接口上使用泛型:
interface KeyValueData<K, V> {
key: K
value: V
compareTo(other:K):boolean
}
其他更高级的用法,可以参考官方文档,或者学习Java语言中泛型的使用!
声明文件
当我们使用第三方库时,我们需要引入其声明文件后,才能获得其对应的代码补全,参数提示灯功能。
声明文件中的声明语句:
declare var JQuery: (selector: string) => any
一般这种声明语句会集中放置在一个扩展名为.d.ts
的文件中(即声明文件),TypeScript编译器会自动扫描检查项目中所有的声明文件!
当我们需要对应库的代码提示时,可以使用npm下载对应的声明文件。
例如下载JQuery的声明文件
npm install @type/jquery --save-dev
console.log(${arg1} <= ${arg2}
)
}
}
}
compare(‘Hello’, ‘TypeScript’) // Hello <= TypeScript
compare(10, 11) // 10 <= 11
**重载函数的实现并不参与类型检查**,只有重载定义的函数才会被检查!!
```typescript
compare(10, 'Hello') // ERROR
compare('Hola', 10) // ERROR
泛型
在Java中我们使用泛型,能够让方法针对多种类型的参数值都可以应对!TypeScript也是如此,使用泛型后一套代码逻辑可以在多种类型的参数下进行利用。大大提供了代码的复用率!
在接口上使用泛型:
interface KeyValueData<K, V> {
key: K
value: V
compareTo(other:K):boolean
}
其他更高级的用法,可以参考官方文档,或者学习Java语言中泛型的使用!
声明文件
当我们使用第三方库时,我们需要引入其声明文件后,才能获得其对应的代码补全,参数提示灯功能。
声明文件中的声明语句:
declare var JQuery: (selector: string) => any
一般这种声明语句会集中放置在一个扩展名为.d.ts
的文件中(即声明文件),TypeScript编译器会自动扫描检查项目中所有的声明文件!
当我们需要对应库的代码提示时,可以使用npm下载对应的声明文件。
例如下载JQuery的声明文件
npm install @type/jquery --save-dev
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步