JS/TS 中有哪些数据(data)类型(type)

  • JS
    • null, undefined, string, number, boolean, bigint, symbol, obejct(含 Array、Function、Date…)
  • TS
    • 以上所有,加上 void, never, enum, unknown, any,再加上自定义类型 type, interface

如何理解 TS 的数据类型

和 JS 的区别在于,JS 说到数据类型的时候,是说这个值是字符串,这个值就是数字,

那么 TS 呢,指的是一类数据的类型,而不是一个,所以说得从集合的角度来理解
例如:

  • number 是指 1 | 1.1 | 1.2 | …. | 2 | …
  • string 是指 a | b | … | z | …
  • boolean 是指 true | false
  • Object 是指 {…} | Array | Function | String | Number | … | RegExp | …

注意:Object 这里 为什么还会有 String,Number?
这里涉及到 JS 的历史遗留知识,42 VS new Number(42)
基本没有人会用 new Number(42) 来表示“42”
42 就是一个普通的值
而 new Number(42),则表示这是一个对象:

1
2
3
4
5
6
7
8
{
constructor: ....,
toFixed: ...,
toString: ...,
valueOf: function() {
return 42
}
}

但是 (42).toFixed(),为什么可以正常运行,明显不合逻辑
当然是因为 JS 的包装对象,在你写上述代码的时候,JS 帮你做了这四步:

  1. let temp = new Number(42); 2. value = temp.toFixed(2); 3. 删除 temp; 4. 返回 value;

所以 JS 中的 Number, String, Boolean 只用于包装对象,正常来说不会用到他们,当然在 TS 里也不用

那么问题又来了,var n = 42; n.xxx = 2; console.log(n.xxx),会打印出什么呢?

用类型签名和Rercord描述对象

1
2
3
4
5
6
type A = Object

const a:A = 1
const a1:A = () => {}
const a2:A = {}
const a3:A = /a+b/

这和 any 有什么区别嘛,所以我们一般在 TS 里不用 Object

那么用什么呢?

  • 用 class / constructor 描述,例如:const a: Function = () => {}
  • 用 type 或 interface 描述,例如:type Person = { name: string; age: number; },我们比较常用的是索引签名 type A = { [k: string]: number }(这个语法特别丑),所以我们一般用 Record<string, number>

思考:key 的类型可以不是 string/symbol 吗?

试一下就知道了

1
2
3
4
5
6
type A = Record<number, number>

const a: A = {
name: 1,
123: 6,
}

当 key 的类型是 number 的时候,虽然 js 不支持,但 ts 只会做字面量上的检查

用[]和Array泛型来描述数组对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type A = string[]
const a: A = ['h', 'i']
// 等价于
type A = Array<string>
const a: A = ['h', 'i']

type D = [string, 'string'] // 二元组
const d: D = ['h', 'i']

type D = [string, 'string', 'string'] // 三元组
const d: D = ['h', 'i', 'n']

type E = [number[], string]
const e: E = [[1,2,3,4], 'abc']

思考

1
2
type A = [1,2,3]
const a: A = ???

只能为 [1,2,3],注意这里的 type A 不是值,1 也是类型,是 number 的子集

描述函数对象

1
2
type FnA = (a, number, b: number) => number
const fnA: FnA = () => 1 // 这里即使不写参数也可以,ts 在这里的检查是松散的,后面会在类型兼容中细讲

返回值为 void 和 undefined 的时候

1
2
3
4
5
type FnA = () => void
const fnA: FnA = () => {}

type FnB = () => undefined
const fnB: FnB = () => {} // 这里报错,似乎与 js 的表现不太一样,按理来说 js 的函数不写返回值,那么默认就是返回 undefined

所以在实践中,我们的函数没有返回值,我们一般写 void

怎么描述函数的 this

1
2
3
4
5
6
7
8
9
// 这里名称必须为 this
type FnWithThis = (this: {name: string; age: number;}, name: string) => void

const fnWithThis: FnWithThis = function() {
console.log('hi', this.name)
}

// 此时调用只能显示指定 this
fnWithThiscall({name: 'hh', age: 18}, 'jack')

由于 Function 不够精确,所以 TS 开发者一般用 () => ? 来描述函数
其他对象一般直接用 class 来描述

其他对象

1
2
3
4
5
6
7
const d: Date = new Date()
const r: RegExp = /ab+c/
const r2: RegExp = new RegExp('ab+c')
const m: Map<string, number> = new Map()
const wm: WeakMap<{name: string}, number> = new WeakMap()
const s: Set<number> = new Set()
const ws: Set<{name: string}, number> = new WeakSet()

any、unknown 是什么?

any 更像是所有的值的集合(也不准确),unknown 和 any 差不多,只是你不收窄类型你就无法使用

官方文档中 any 的解释为如果你不想让你的类型报错你可以使用这个类型

never 是什么

可以理解为空集,哪有什么用呢?用来做检查

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
type A = string & number
// string 和 number 的交集是什么呢?就是 never
// 一般没人会这么写
type B = string | number | boolean
const b:B

if (typeof b === 'string') {
a.split('')
} else if (typeof b === 'number') {
a.toFixed(2)
} else if (typeof a === 'boolean') {
a.valueOf()
} else {
console.log('没了')
// 此时这里的 a 就是 never 了,
// 上面已经把该有的可能都排除了,但是如果让你告诉我这里的 a 是什么类型,怎么解释
// 为了解释这种类型,所以才有 never
}