学习笔记|TypeScript

本文最后更新于:1 个月前

1.是什么 & 为什么

TypeScript 是什么:微软在 JavaScript 的基础上,添加了一些类型的定义,推出了 TypeScript。

为什么要使用 TypeScript,而不使用 JavaScript:

  • JavaScript 的缺点

    • JavaScript 1995 年被开发,至今版本很多(ES5、ES6)。

    • JavaScript 是弱类型语言。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 举几个例子
// 很灵活,可以随便改变类型,并添加字段。
a = {
name: 'js',
}

a.number = 2

console.log(a) // {name: 'js', number: 2}

// 坑
console.log(2 == '2') // true
console.log(10 + 20) // 30
console.log(10 + '20') // "1020"
console.log([1,2,3] + 4) // "1,2,34"
  • TypeScript 的优点

    • 有类型定义。
    • 兼容 JavaScript,是 JavaScript 的超集。
    • 兼容的方式:将 TypeScript 编译成 JavaScript 运行。

2.TypeScript 的基础语法

大部分与 JavaScript 类似。

2.1 代码规范

整个项目规范要统一。

行末分号:加不加分号均可。

字符串的引号:双引号、单引号均可。

1
2
3
4
5
let name = 'Tom'
let name = "Tom"

console.log(name)
console.log(name);

2.2 基础数据类型

(1)变量定义

只使用 let、const,不要使用 var。

1
2
let anExampleVariable = "abc"
const anExampleNum = 123

(3)声明类型

变量名后面接类型。但是最好不要写,影响代码简洁,让编译器来推断。

1
2
3
let anExampleVariable: string = "abc"
let anExampleNum: number = 123
let anExampleBool: boolean = true

(2)数据类型

  • string

  • number

  • boolean

  • literal type

    类似于枚举类型,变量只能是这些类型。

    这样在编写代码的时候,就会有提示和内容限制。JavaScript 就需要花费精力去判断 变量 有哪些可能出现的值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 限制变量赋值
let httpStatus: 200 | 404 | 500 | '200' | '404' | '500' = '200'

// 限制函数参数,其他非法参数传入会报错。
function f(answer: 'yes' | 'no' | 'maybe') {
console.log(answer)
}

// 注意
let s1 = httpStatus // 此时 s1 的类型是 '200'。因为编译器判断此时 httpStatus 只可能是 '200'。
let s2: string = httpStatus // 此时 s2 的类型是 string。

httpStatus = s1 // 正确
httStatus = s2 // 报错:Type 'string' is not assignable to type '200 | 404 | 500 | "200" | "404" | "500"'. 因为此时 s2 是 string 类型,无法赋值给 literal type。
  • union of types

    可能是多种类型。例如:status 可能是 string,也可能是 number。

1
2
3
4
function f(httpStatus: 200 | 404 | 500 | '200' | '404' | '500') {
let status: string | number = httpStatus
// let status: string = httpStatus // 错误,因为 httpStatus 可能是 string,也可能是 number。
}
  • any

    动态类型,意味着放弃 TypeScript 的所有类型检查,和 JavaScript 一样。

    放弃类型检查,编写时不报错,但是运行时会报错。

1
2
3
4
5
let a: any = 'abc'
a = 123 // 放弃类型检查。
a.name = '123' // 运行时会报错。

console.log(a)
  • undefined

    undefined 类型变量不能被赋值为任何类型。可以与 literal type 结合使用,在对象类型、函数、接口中都会用到。

1
2
3
4
5
6
7
8
let s: undefined = undefined
s = "111" // 会报错:Type '"111"' is not assignable to type 'undefined'.
console.log(s) // 但也会打印出 "111"

// 正确使用方式
function f(answer: 'yes' | 'no' | 'maybe' | undefined) { // answer 可能是 undefined。
console.log(answer)
}

2.3 逻辑控制

(1)if

一律使用 ===!== 。因为 == 不同类型的变量也可以比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 传入参数限定后,不用担心传入其他字符。
function processHttpStatus(s: '200' | '404' | '500') {
if (s === '200') {
console.log('ok')
} else if (s === '404') {
console.log('not found')
} else if (s === '500') {
console.log('internal server error')
}
}

processHttpStatus('200')

processHttpStatus('201') // 运行会报错,Argument of type '"201"' is not assignable to parameter of type '"200" | "404" | "500"'.

例:如果需要将传入的 number,全部转化为 string。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function processHttpStatus(s: '200' | '404' | '500' | 200 | 404 | 500) {

// key code: string to number.
let statusStr = ''
if (typeof s === 'number') {
statusStr = s.toString()
} else {
statusStr = s
}

if (statusStr === '200') {
console.log('ok')
} else if (statusStr === '404') {
console.log('not found')
} else if (statusStr === '500') {
console.log('internal server error')
}
}

processHttpStatus('200')
processHttpStatus(200)

其中,转换为 strng 的代码也可以写成三元表达式:

1
2
// 由于 statusStr 定义之后,就不需要修改了,所以使用 const 修饰。
const statusStr = typeof s === 'string' ? s.toString() : s

(2)switch

case 动作后需要加 break。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function processHttpStatus(s: '200' | '404' | '500' | 200 | 404 | 500) {

// key code: string to number.
let statusStr = ''
if (typeof s === 'number') {
statusStr = s.toString()
} else {
statusStr = s
}

switch(statusStr) {
case '200':
console.log('ok')
break
case '404':
console.log('not found')
break
case '500':
console.log('internal server error')
break
default:
console.log('unknown error')
break
}
}

processHttpStatus('200')
processHttpStatus(200)

(3)for

1
2
3
4
5
6
let sum = 0
for (let i = 0; i < 100; i++) {
sum += i
}

console.log(sum)

(4)while

1
2
3
4
5
6
7
8
let sum = 0
let i = 0
while (i < 100) {
sum += i
i++
}

console.log(sum)

(5)try/catch

例:如果 i 是 17 的倍数,就抛出错误。使用 try/catch 就能保证,即使出错,程序也不会立即退出。

1
2
3
4
5
6
7
8
9
10
11
12
13
let sum = 0
for (let i = 0; i < 100; i++) {
try {
sum += i
if (i % 17 === 0) {
throw `bad number ${i}` // 反引号中,使用 ${} 来标记变量。
}
} catch (err) {
console.error(err)
}
}

console.log(sum)

2.4 枚举

枚举类型是 typesript 特有的数据类型,javascript 只能通过类来模拟枚举类型。

实际使用中,可以使用 literal type 或 enum。

  • 枚举类型定义时,每个类型用 逗号 分隔。
  • 不给每个类型设定值,默认是 0、1、2…
1
2
3
4
5
6
7
8
9
10
11
12
enum HTTPStatus {
OK,
NOT_FOUNT,
INTERNAL_SERVER_ERROR,
}

function processHTTPStatus(s: HTTPStatus) {
console.log(s) // 0
console.log(HTTPStatus[s]) // 'OK'
}

processHTTPStatus(HTTPStatus.OK)
  • 可以给每个类型设定值。

    值既可以是数字,也可以是字符串。但是字符串的时候,无法使用 HTTPStatus[s],因为字符串不能作为索引。

1
2
3
4
5
6
7
8
9
10
11
12
enum HTTPStatus {
OK = 200,
NOT_FOUNT = 404,
INTERNAL_SERVER_ERROR = 500,
}

function processHTTPStatus(s: HTTPStatus) {
console.log(s) // 200
console.log(HTTPStatus[s]) // 'OK'
}

processHTTPStatus(HTTPStatus.OK)

2.5 数组

(1)定义

1
2
3
4
let arr = [1, 2, 3]

console.log(arr) // [1, 2, 3]
// 此时编译器判断 arr 的类型是 number[]

(2)类型限定

1
2
3
4
5
6
let arr: number[] = [1, 2, 3]
let arr: any[] = [1, 'a', true] // 任意类型

let arr: Array<number> = [1, 2, 3]

let arr: (number | string)[] = [1, ,2, 3, 'a']

(3)数组长度

1
2
let arr = [1, 2, 3]
console.log(arr.length) // 数组长度

(4)取值

注:数组下标越界,不会报错。

1
2
3
4
let arr = [1, 2, 3]
console.log(arr[0], a[1]) // 1, 2

console.log(arr[4]) // 数组下标越界了,并不会报错,会打印出 undefined

(5)空数组

1
let arr:number[] = []   // 空数组定义,一定要声明类型,否则默认类型为 any[]。

通过长度判断是否为空数组:

1
2
3
4
5
6
7
let arr:number[] = []

if (arr.length !== 0) {
console.log('arr is not empty')
} else {
console.log('arr is empty')
}

(6)尾部插入 push

1
2
3
4
let arr:number[] = [1, 2, 3]
arr.push(4)

console.log(arr) // [1, 2, 3, 4]

(7)尾部删除 pop

1
2
3
4
let arr:number[] = [1, 2, 3]
arr.pop()

console.log(arr) // [1, 2]

(8)首部插入 unshift

1
2
3
4
let arr:number[] = [1, 2, 3]
arr.unshift(4)

console.log(arr) // [4, 1, 2, 3]

(9)首部删除 shift

1
2
3
4
let arr:number[] = [1, 2, 3]
arr.shift()

console.log(arr) // [2, 3]

(10)数组的 const

1
2
3
4
5
6
const arr:number[] = [1, 2, 3]

arr[2] = 0
arr.push(4) // 正确,可以更改当前数组内容

arr = [1, 2] // 错误,不能赋值(类似于 cpp 中指针,不可以指到别的数组)

(11)截取

slice 左闭右开,返回截取出来的数组,不会修改原数组。

1
2
3
4
5
6
7
8
9
const arr:number[] = [0, 1, 2, 3, 4, 5, 6]

console.log(arr.slice(2, 5)) // [2, 3, 4] ,范围是 [2, 5)

console.log(arr.slice(2)) // 2 到 末尾,[2, 3, 4, 5, 6]

console.log(arr.slice(2, 100)) // 数组下标越界,不会报错,还是 2 到 末尾

console.log(arr) // 不会修改原数组,[0, 1, 2, 3, 4, 5, 6]

(12)更改部分元素

  • 2 个参数是删除元素

​ splice 删除部分元素,并返回被删除的元素。

1
2
3
4
5
6
const arr:number[] = [0, 1, 2, 3, 4, 5, 6]

let deleted = arr.splice(3, 2) // 从下标为 3 的元素开始,删除 2 个元素

console.log(deleted) // [3, 4] // 删除的元素
console.log(arr) // [0, 1, 2, 5, 6]
  • 3 个以上元素是删除,并添加元素。
1
2
3
4
5
6
const arr:number[] = [0, 1, 2, 3, 4, 5, 6]

let deleted = arr.splice(3, 2, 0, 0, 0) // 从下标为 3 的元素开始,删除 2 个元素,并在这个位置添加三个 0

console.log(deleted) // [3, 4] // 删除的元素
console.log(arr) // [0, 1, 2, 0, 0, 0, 5, 6]

(13)查找元素

1
2
3
4
5
6
7
8
9
10
const arr:number[] = [8, 2, 4, 9, 0, 1, 4]

console.log(arr.indexOf(4)) // 2 // 查找元素 4,返回 下标值。
console.log(arr.indexOf(4, 3)) // 6 // 从下标为 3 开始查找。

console.log(arr.lastIndexOf(4)) // 6 // 从后往前找
console.log(arr.lastIndexOf(4, 5)) // 2 // 从下标为 5 开始,从后往前找

console.log(arr.indexOf(3)) // -1 // 找不到返回 -1
console.log(arr.lastIndexOf(3)) // -1 // 找不到返回 -1

(14)排序

  • 直接调用 sort 是按照字典序排序,主要是用来排字符串的。
1
2
3
4
const arr:number[] = [8, 2, 14, 1]
arr.sort()

console.log(arr) // [1, 14, 2, 8] // 字典序,排序
  • 排序 number 数组,需要传入回调函数。
1
2
3
4
5
6
7
8
function compareNumber(a:number, b:number) {
return a - b
}

const a = [2,13,21,4,5,1,42,3,7,9]

a.sort(compareNumber)
console.log(a)

(15)元组

1
2
3
4
const arr = [1, 2, 3]
const [a1, a2, a3] = arr

console.log(a1, a2, a3)

(16)分隔 split

1
2
3
4
5
let s = 'a,b,c,d,1,2,3,'

let arr = s.split(',')

console.log(arr) // ["a", "b", "c", "d", "1", "2", "3", ""]

(17)连接 join

1
2
3
4
5
const arr = [1, 2, 3]

console.log(arr.join()) // "1,2,3"
console.log(arr.join('-')) // "1-2-3" // join 参数表示 分隔符
console.log(arr.join('')) // "123"

2.6 对象

不需要有类,可以直接手写对象 object。

(1)定义

对于没有初值的字段,可以赋值为 undefined;但该字段类型并不真的是 undefined,所以需要指明类型为 union of types。

1
2
3
4
5
6
7
8
const emp1 = {
name: 'john',
gender: 'male' as ('male' | 'female'),
salary: 8000,
bonus: undefined as (undefined | number) // bonus 还未定义,需要指定类型为 (undefined | number)。
}

console.log(emp1) // { name: { first: 'san', last: 'zhang' }, gender: 'male', salary: 8000, bonus: undefined }

(2)嵌套

变量的值可以是 object、数组等各种类型。

1
2
3
4
5
6
7
8
9
const emp1 = {
name: {
first: 'san',
last: 'zhang',
},
gender: 'male',
}

console.log(emp1) // { name: { first: 'san', last: 'zhang' }, gender: 'male' }

(3)取值

可根据关系,直接 “点” 出来,或使用键值对的方式。

1
2
console.log(emp1.name.frist)
console.log(emp1['name'])

(4)JSON

JSON = JavaScript Object Notation。

  • 在 JSON 中,键必须是字符串,由双引号包围。{ "name":"Bill Gates" }
    • 在 JavaScript 中,键可以是字符串、数字或标识符名称。{ name:'Bill Gates' }

(5)JSON 与 string 转化

JSON.stringify(object)JSON.parse(string)

注意: 下面的例子中,emp2 只能是 any 类型,因为编译器无法判断 JSON.parse(string) 会返回什么类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const emp1 = {
name: {
first: 'san',
last: 'zhang',
},
gender: 'male',
}
console.log(emp1)

const s:string = JSON.stringify(emp1)
console.log(s)

const emp2 = JSON.parse(s) // emp2 是 any 类型。
console.log(emp2)

(6)对象的比较

不能直接比较,只能分别比较不同的字段。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const emp1 = {
name: {
first: 'san',
last: 'zhang',
},
gender: 'male',
}
const emp2 = {
name: {
first: 'san',
last: 'zhang',
},
gender: 'male',
}

console.log(emp1 === emp2) // false

2.7 函数

(1)函数定义

函数返回值可以声明,也可以不声明;但如果声明了返回值类型,不返回将报错。

1
2
3
4
5
6
7
8
9
function add(a:number, b:number): number {  // 声明返回值
return a + b
}

function add1(a:number, b:number) { // 未声明返回值
return a + b
}

console.log(add(2,3))

(2)可选参数

可选参数后加 ? ,下面代码中,如果 c 未传入,类型就是 undefined,所以需要判断一下。

判断可选参数的简便写法:return c ? a + b + c : a + breturn a + b + (c || 0)

1
2
3
4
5
6
7
8
9
10
function add(a:number, b:number, c?: number): number {  //  c 是可选参数
if (c) {
return a + b + c // 编译器非常聪明,判断之后,此处 c 的类型是 number。
} else {
return a + b
}
}

console.log(add(2, 3))
console.log(add(2, 3, 4))

(3)默认参数

参数可以设置默认值。

1
2
3
4
5
6
7
8
9
10
function add(a:number, b:number, c?: number, d: number=0): number {   // d 是默认参数
if (c) {
return a + b + c + d
} else {
return a + b + d
}
}

console.log(add(2, 3))
console.log(add(2, 3, 4, 5))

(4)可变参数列表

1
2
3
4
5
6
7
8
9
10
function add(a:number, b:number, c?: number, d: number=0, ...e: number[]): number {   // e 是可变参数
let sum = a + b + (c || 0) + d
for (let i = 0; i < e.length; i++) {
sum += e[i]
}
return sum
}

console.log(add(2, 3))
console.log(add(2, 3, 4, 5, 6, 7, 8))

(5)数组参开当可选参数列表

1
2
3
4
5
6
7
8
9
10
function add(a:number, b:number, ...e:number[]): number {
let sum = a + b
for (let i = 0; i < e.length; i++) {
sum += e[i]
}
return sum
}

const numberList = [5, 6, 7, 8]
console.log(add(1, 2, 3, 4, ...numberList)) // 只需要在 数组变量 前加 ...

(6)函数重载(不推荐使用)

推荐使用可变参数列表、默认参数、可选参数、union type。

typescript 的函数重载很:有多个声明,但只有一个函数实现,这个函数要兼容所有函数声明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// 例子一
function add(a: number, b:number):number
function add(a:number, b:number, ...e:number[]): number

function add(a:number, b:number, ...e:number[]): number {
let sum = a + b
for (let i = 0; i < e.length; i++) {
sum += e[i]
}
return sum
}

const numberList = [5, 6, 7, 8]
console.log(add(1, 2, 3, 4, ...numberList))
console.log(add(1, 2))

// 例子二
function add(x:string,y:string):number;
function add(x:number, y:number):number;

function add(x:string|number, y: number|string): number | string{
if (typeof x === 'string') {
return (+x) + (+y); // string 转 number,也可以写成:Number(x) + Number(y)
}else {
return x + (y as number); // 这里要说明一下 y 是 number,因为前面只判断了 x 的类型。
}
}

console.log(add(1, 2))
console.log(add('2', '3'))

(7)对象类型作为参数

不使用对象类型参数时,调用函数时,参数太多,写代码和看代码的人都不容易明确每个参数的含义。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 不使用对象类型参数
function sendRequest(
url: string,
method: 'GET' | 'POST' | 'PUT',
header: object,
data: string,
requireAuth: boolean,
retey: boolean,
retryTimeout: number) {
// ...
console.log("success")
}

sendRequest('https://www.bing.com', 'GET', {contentType: 'application/json',}, '{"name":"john"}', true, true, 300000) // 不便写和读

使用对象类型参数时,增加代码可读性。params:Parameters,参数。

TypeScript/JavaScript 创建对象很方便,所以要有使用对象类型的意识。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 使用对象类型参数
function sendRequest(params: { // params
url: string,
method: 'GET' | 'POST' | 'PUT',
header: object,
data: string,
requireAuth: boolean,
retey: boolean,
retryTimeout: number
}) {
// ....
console.log("success")
}

sendRequest({ // 参数明确,有良好可读性。
url: 'https://www.bing.com',
method: 'GET',
header: {
contentType: 'application/json',
},
data: '{"name":"john"}',
requireAuth: true,
retey: true,
retryTimeout: 300000
})

2.8 对象的方法

对象中可以定义方法,this 关键字指代当前大括号的这个对象。

注意:this 有坑,当嵌套的对象过多时,this 指代谁就不一定了。解决办法:函数式编程可以解决。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const emp1 = {
name: 'john',
gender: 'male' as ('male' | 'female'),
salary: 8000,
bonus: undefined as (undefined | number),
performance: 3.5,
updateBonus() {
if (!this.bonus) { // this 指代 emp1
this.bonus = this.salary * this.performance
}
}
}

console.log(emp1.bonus)
emp1.updateBonus()
console.log(emp1.bonus)

3.函数式编程

初学者很难理解函数式编程的优势,在学习过程中,可以先理解逻辑,在实际编码中了解其优势。

graph LR
A(函数式编程)--> B1(函数是一等公民)
B1 --> C1(变量类型可以是函数)
B1 --> C2(值可以是函数)
B1 --> C3(对象字段的值可以是函数)
B1 --> C4(函数参数可以是函数)
B1 --> C5(函数返回值可以是函数)
A --> B2(高阶函数)
A --> B3(闭包)
A --> B4(部分应用函数)
A --> B5(惰性计算)
A --> B6(引用透明性)
A --> B7(无副作用)

3.1 函数是一等公民

(1)变量可以是函数

  • 定义变量时,变量类型为:let compareNumber: (a: number, b: number) => number,第一出现时,编译器根据函数,确定了这个函数的参数和返回值类型。

  • 再给 compareNumber 赋值的时候,只能赋值 (a: number, b: number) => number 类型函数。

1
2
3
4
5
6
7
8
9
10
11
12
// 排序数组 a
let a = [2,13,21,4,5,1,42,3,7,9]

let compareNumber = function(a:number, b:number) { // 变量 compareNumber 是函数
return a - b
}
a.sort(compareNumber)

compareNumber = function(a:number, b:number) {
return b - a
}
a.sort(compareNumber)

(2)值(literal)可以是函数

如上面的代码,函数在等号右边,是作为值。

(3)对象的字段的值可以是函数

1
2
3
4
5
6
7
8
9
const emp1 = {
name: 'john',
salary: 8000,
getBonus: function (performance: number) { // 对象字段是函数
return this.salary * performance
}
}

console.log(emp1.getBonus(1.1))

(4)函数参数可以是函数

1
2
3
4
5
6
7
// 排序数组 a
function compareNumber(a:number, b:number) {
return a - b
}

let a = [2,13,21,4,5,1,42,3,7,9]
a.sort(compareNumber) // 函数 compareNumber 是参数

(5)函数返回值可以是函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 排序数组 a
function compareNumber(params: {smallerFirst: boolean}) {
if (params.smallerFirst) {
return (a:number, b:number) => { // 函数 compareNumber() 的返回值是函数
console.log(a, b)
return a - b
}
} else {
return (a:number, b:number) => { // 函数 compareNumber() 的返回值是函数
console.log(a, b)
return b - a
}
}
}

let a = [2,13,21,4,5,1,42,3,7,9]
a.sort(compareNumber({smallerFirst:true}))

3.2 补充:lambda expression

expression:表达式

在 JS/TS 中,叫 Arrow Function(箭头函数)

  • 箭头后面直接接表达式。
1
2
3
4
let a = [2,13,21,4,5,1,42,3,7,9]

let compareNumber = (a:number, b:number) => a-b // 箭头函数
a.sort(compareNumber)
  • 箭头后面接一个函数体
1
2
3
4
5
6
7
let a = [2,13,21,4,5,1,42,3,7,9]

let compareNumber = (a:number, b:number) => { // 箭头函数
console.log(a, b)
return a-b
}
a.sort(compareNumber)

3.2 高阶函数

在数学和计算机科学中,高阶函数是至少满足下列一个条件的函数:

  • 接受一个或多个函数作为输入;
  • 输出一个函数。

在 3.1 函数是一等公民 中,可以看到,TS 有高阶函数的能力。


本博客所有文章均个人原创,除特别声明外均采用 CC BY-SA 4.0协议,转载请注明出处!