一、简单基础类型
在说TypeScript数据类型之前,先来看看在TypeScript中定义数据类型的基本语法。
在语法层面,缺省类型注解的 TypeScript 与 JavaScript 完全一致。因此,可以把 TypeScript 代码的编写看作是为 JavaScript 代码添加类型注解。
在 TypeScript 语法中,类型的标注主要通过类型后置语法来实现:“变量: 类型”
let num = 996 let num: number = 996 复制代码
上面代码中,第一行的语法是同时符合 JavaScript 和 TypeScript 语法的,这里隐式的定义了num是数字类型,我们就不能再给num赋值为其他类型。而第二行代码显式的声明了变量num是数字类型,同样,不能再给num赋值为其他类型,否则就会报错。
在 JavaScript 中,原始类型指的是非对象且没有方法的数据类型,包括:number、boolean、string、null、undefined、symbol、bigInt。
它们对应的 TypeScript 类型如下:
JavaScript原始基础类型 | TypeScript类型 |
number | number |
boolean | boolean |
string | string |
null | null |
undefined | undefined |
symbol | symbol |
bigInt | bigInt |
需要注意number和Number的区别:TypeScript中指定类型的时候要用 number ,这是TypeScript的类型关键字。而 Number 是 JavaScript 的原生构造函数,用它来创建数值类型的值,这两个是不一样的。包括string、boolean等都是TypeScript的类型关键字,而不是JavaScript语法。
1. number
TypeScript 和 JavaScript 一样,所有数字都是浮点数,所以只有一个 number 类型。
TypeScript 还支持 ES6 中新增的二进制和八进制字面量,所以 TypeScript 中共支持2、8、10和16这四种进制的数值:
let num: number; num = 123; num = "123"; // error 不能将类型"123"分配给类型"number" num = 0b1111011; // 二进制的123 num = 0o173; // 八进制的123 num = 0x7b; // 十六进制的123 复制代码
2. string
字符串类型可以使用单引号和双引号来包裹内容,但是如果使用 Tslint 规则,会对引号进行检测,使用单引号还是双引号可以在 Tslint 规则中进行配置。除此之外,还可以使用 ES6 中的模板字符串来拼接变量和字符串会更为方便。
let str: string = "Hello World"; str = "Hello TypeScript"; const first = "Hello"; const last = "TypeScript"; str = `${first} ${last}`; console.log(str) // 结果: Hello TypeScript 复制代码
3. boolean
类型为布尔值类型的变量的值只能是true或者false。除此之外,赋值给布尔值的值也可以是一个计算之后结果为布尔值的表达式:
let bool: boolean = false; bool = true; let bool: boolean = !!0 console.log(bool) // false 复制代码
4. null和undefined
在 JavaScript 中,undefined和 null 是两个基本数据类型。在 TypeScript 中,这两者都有各自的类型,即 undefined 和 null,也就是说它们既是实际的值,也是类型。这两种类型的实际用处不是很大。
let u: undefined = undefined; let n: null = null; 复制代码
注意,第一行代码可能会报一个tslint的错误:Unnecessary initialization to 'undefined'
,就是不能给一个变量赋值为undefined。但实际上给变量赋值为undefined是完全可以的,所以如果想让代码合理化,可以配置tslint,将"no-unnecessary-initializer
"设置为false
即可。
默认情况下,undefined 和 null 是所有类型的子类型,可以赋值给任意类型的值,也就是说可以把 undefined 赋值给 void 类型,也可以赋值给 number 类型。当在 tsconfig.json
的"compilerOptions"里设置为 "strictNullChecks": true 时,就必须严格对待了。这时 undefined 和 null 将只能赋值给它们自身或者 void 类型。这样也可以规避一些错误。
5. bigInt
BigInt是ES6中新引入的数据类型,它是一种内置对象,它提供了一种方法来表示大于 2- 1 的整数,BigInt可以表示任意大的整数。
使用 BigInt
可以安全地存储和操作大整数,即使这个数已经超出了JavaScript构造函数 Number 能够表示的安全整数范围。
我们知道,在 JavaScript 中采用双精度浮点数,这导致精度有限,比如 Number.MAX_SAFE_INTEGER
给出了可以安全递增的最大可能整数,即2- 1
,来看一个例子:
const max = Number.MAX_SAFE_INTEGER; const max1 = max + 1 const max2 = max + 2 max1 === max2 // true 复制代码
可以看到,最终返回了true,这就是超过精读范围造成的问题,而BigInt
正是解决这类问题而生的:
const max = BigInt(Number.MAX_SAFE_INTEGER); const max1 = max + 1n const max2 = max + 2n max1 === max2 // false 复制代码
这里需要用 BigInt(number)
把 Number 转化为 BigInt
,同时如果类型是 BigInt
,那么数字后面需要加 n
。
在TypeScript中,number
类型虽然和 BigInt
都表示数字,但是实际上两者类型是完全不同的:
declare let foo: number; declare let bar: bigint; foo = bar; // error: Type 'bigint' is not assignable to type 'number'. bar = foo; // error: Type 'number' is not assignable to type 'bigint'. 复制代码
6. symbol
symbol我们平时用的比较少,所以可能了解也不是很多,这里就详细来说说symbol。
(1)symbol 基本使用
symbol 是 ES6 新增的一种基本数据类型,它用来表示独一无二的值,可以通过 Symbol 构造函数生成。
const s = Symbol(); typeof s; // symbol 复制代码
注意:Symbol 前面不能加 new关键字,直接调用即可创建一个独一无二的 symbol 类型的值。
可以在使用 Symbol 方法创建 symbol 类型值的时候传入一个参数,这个参数需要是一个字符串。如果传入的参数不是字符串,会先自动调用传入参数的 toString 方法转为字符串:
const s1 = Symbol("TypeScript"); const s2 = Symbol("Typescript"); console.log(s1 === s2); // false 复制代码
上面代码的第三行可能会报一个错误:This condition will always return 'false' since the types 'unique symbol' and 'unique symbol' have no overlap. 这是因为编译器检测到这里的 s1 === s2 始终是false,所以编译器提醒这代码写的多余,建议进行优化。
上面使用Symbol创建了两个symbol对象,方法中都传入了相同的字符串,但是两个symbol值仍然是false,这就说明了 Symbol 方法会返回一个独一无二的值。Symbol 方法传入的这个字符串,就是方便我们区分 symbol 值的。可以调用 symbol 值的 toString 方法将它转为字符串:
const s1 = Symbol("Typescript"); console.log(s1.toString()); // 'Symbol(Typescript)' console.log(Boolean(s)); // true console.log(!s); // false 复制代码
在TypeScript中使用symbol就是指定一个值的类型为symbol类型:
let a: symbol = Symbol() 复制代码
TypeScript 中还有一个 unique symbol 类型,它是symbol的子类型,这种类型的值只能由Symbol()
或Symbol.for()
创建,或者通过指定类型来指定变量是这种类型。这种类型的值只能用于常量的定义和用于属性名。需要注意,定义unique symbol类型的值,必须用 const 而不能用let来声明。下面来看在TypeScript中使用Symbol值作为属性名的例子:
const key1: unique symbol = Symbol() let key2: symbol = Symbol() const obj = { [key1]: 'value1', [key2]: 'value2' } console.log(obj[key1]) // value1 console.log(obj[key2]) // error 类型“symbol”不能作为索引类型使用。 复制代码
(2)symbol 作为属性名
在ES6中,对象的属性是支持表达式的,可以使用于一个变量来作为属性名,这对于代码的简化有很多用处,表达式必须放在大括号内:
let prop = "name"; const obj = { [prop]: "TypeScript" }; console.log(obj.name); // 'TypeScript' 复制代码
symbol 也可以作为属性名,因为symbol的值是独一无二的,所以当它作为属性名时,不会与其他任何属性名重复。当需要访问这个属性时,只能使用这个symbol值来访问(必须使用方括号形式来访问):
let name = Symbol(); let obj = { [name]: "TypeScript" }; console.log(obj); // { Symbol(): 'TypeScript' } console.log(obj[name]); // 'TypeScript' console.log(obj.name); // undefined 复制代码
在使用obj.name访问时,实际上是字符串name,这和访问普通字符串类型的属性名是一样的,要想访问属性名为symbol类型的属性时,必须使用方括号。方括号中的name才是我们定义的symbol类型的变量name。
(3)symbol 属性名遍历
使用 Symbol 类型值作为属性名,这个属性是不会被 for…in遍历到的,也不会被 Object.keys() 、 Object.getOwnPropertyNames() 、 JSON.stringify() 等方法获取到:
const name = Symbol("name"); const obj = { [name]: "TypeScript", age: 18 }; for (const key in obj) { console.log(key); } // => 'age' console.log(Object.keys(obj)); // ['age'] console.log(Object.getOwnPropertyNames(obj)); // ['age'] console.log(JSON.stringify(obj)); // '{ "age": 18 } 复制代码
虽然这些方法都不能访问到Symbol类型的属性名,但是Symbol类型的属性并不是私有属性,可以使用 Object.getOwnPropertySymbols
方法获取对象的所有symbol类型的属性名:
const name = Symbol("name"); const obj = { [name]: "TypeScript", age: 18 }; const SymbolPropNames = Object.getOwnPropertySymbols(obj); console.log(SymbolPropNames); // [ Symbol(name) ] console.log(obj[SymbolPropNames[0]]); // 'TypeScript' 复制代码
除了这个方法,还可以使用ES6提供的 Reflect 对象的静态方法 Reflect.ownKeys ,它可以返回所有类型的属性名,Symbol 类型的也会返回:
const name = Symbol("name"); const obj = { [name]: "TypeScript", age: 18 }; console.log(Reflect.ownKeys(obj)); // [ 'age', Symbol(name) ] 复制代码
(4)symbol 静态方法
Symbol 包含两个静态方法, for 和 keyFor 。
1)Symbol.for()
用Symbol创建的symbol类型的值都是独一无二的。使用 Symbol.for 方法传入字符串,会先检查有没有使用该字符串调用 Symbol.for 方法创建的 symbol 值。如果有,返回该值;如果没有,则使用该字符串新创建一个。使用该方法创建 symbol 值后会在全局范围进行注册。
const iframe = document.createElement("iframe"); iframe.src = String(window.location); document.body.appendChild(iframe); iframe.contentWindow.Symbol.for("TypeScript") === Symbol.for("TypeScript"); // true // 注意:如果你在JavaScript环境中这段代码是没有问题的,但是如果在TypeScript开发环境中,可能会报错:类型“Window”上不存在属性“Symbol”。 // 因为这里编译器推断出iframe.contentWindow是Window类型,但是TypeScript的声明文件中,对Window的定义缺少Symbol这个字段,所以会报错, 复制代码
上面代码中,创建了一个iframe节点并把它放在body中,通过这个 iframe 对象的 contentWindow 拿到这个 iframe 的 window 对象,在 iframe.contentWindow上添加一个值就相当于在当前页面定义一个全局变量一样。可以看到,在 iframe 中定义的键为 TypeScript 的 symbol 值在和在当前页面定义的键为'TypeScript'的symbol 值相等,说明它们是同一个值。
2)Symbol.keyFor()
该方法传入一个 symbol 值,返回该值在全局注册的键名:
const sym = Symbol.for("TypeScript"); console.log(Symbol.keyFor(sym)); // 'TypeScript' 复制代码
二、复杂基础类型
看完简单的数据类型,下面就来看看比较复杂的数据类型,包括JavaScript中的数组和对象,以及TypeScript中新增的元组、枚举、Any、void、never、unknown。
1. array
在 TypeScript 中有两种定义数组的方式:
- 直接定义: 通过 number[] 的形式来指定这个类型元素均为number类型的数组类型,推荐使用这种写法。
- 数组泛型: 通过 Array 的形式来定义,使用这种形式定义时,tslint 可能会警告让我们使用第一种形式定义,可以通过在
tslint.json
的 rules 中加入"array-type": [false]
就可以关闭 tslint 对这条的检测。
let list1: number[] = [1, 2, 3]; let list2: Array<number> = [1, 2, 3]; 复制代码
以上两种定义数组类型的方式虽然本质上没有任何区别,但是更推荐使用第一种形式来定义。一方面可以避免与 JSX 语法冲突,另一方面可以减少代码量。
注意,这两种写法中的 number 指定的是数组元素的类型,也可以在这里将数组的元素指定为其他任意类型。如果要指定一个数组里的元素既可以是数值也可以是字符串,那么可以使用这种方式: number|string[]
。