『Typescript系列』 数据类型

112 阅读12分钟

一、基本数据类型

JavaScript原始数据类型Typescript数据类型
Numbernumber
Booleanboolean
Stringstring
Nullnull
Undefinedundefined
Symbolsymbol
bigIntbigInt

类型标注:“变量名:类型”

1.number

Typescript 的number类型支持二、八、十、十六共四种进制的数值:

let num:number;
num = 123;
num = "123";     // error 不能将类型"123"分配给类型"number"
num = 0b1111011; // 二进制的123
num = 0o173;     // 八进制的123
num = 0x7b;      // 十六进制的123

let arr1:number[] = [1,2,3];
let arr2: Array<number> = [1,2,3];

2.string

字符串类型可以使用单引号或者双引号来包裹内容,也可以使用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

let bool: boolean = false;
bool = true;

let bool: boolean = !!0
console.log(bool) // false

第一行代码可能会报一个tslint的错误:Unnecessary initialization to 'undefined',意思是不能给一个变量赋值为undefined,但实际上是可以的,只需要将tslint 配置 "no-unnecessary-initializer 设置为 false" 即可。

默认情况下,undefined 和null 是所有类型的子类型,可以赋值给任意类型的值,也就是说可以把undefined 赋值给 void 类型,也可以赋值给 number 类型,当在 tsconfig.json的 "compilerOptions"里设置为"strictNullChecks": true 时,就表示是严格模式,这时 undefined 和 null 就只能赋值给他们自身或者 void 类型,这样就可以规避一些错误。也许在某处你想传入一个 string或null或undefined,你可以使用联合类型string | null | undefined。

5.bigint

bigint表示任意大的整数,可以安全的操作和存储任意大整数,即使这个数已经超出了JavaScript构造函数Number能够表示的安全整数范围。

在 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我们平时使用较少。

(1)基本用法

symbol表示独一无二的值,可以通过Symbol构造函数生成。

const s = Symbol(); 
typeof s; // symbol

Symbol前面不用加 new关键字,直接调用即可创建一个独一无二的值

可以在使用Symbol方法创建的symbol类型的时候传入一个参数,这个参数需要是一个字符串,如果传入的参数不是字符串,会自动调用传入参数的 toString方法转为字符串:

const s1 = Symbol("TypeScript"); 
const s2 = Symbol("Typescript"); 
console.log(s1 === s2); // false

上面创建了两个symbol对象,方法都传入了相同的字符串,但是两个symbol值仍然是不相等,这就说明了Symbol方法会返回一个独一无二的值。

在Typescript 中使用 symbol创建一个变量

let a: symbol = Symbol()

在Typescript中含有一个unique symbol 类型,他是symbol 的子类型,这种类型的值只能由 Symbol()Symbol.for() 创建,或者通过指定类型来指定变量是这种类型,这种类型的值只能用于常量的定义和用于属性名,需要注意,定义unique symbol 类型的值,必须用 const 而不能用let 来声明,下面来看使用 symbol 作为属性名的例子。

const key1: unique symbol = Symbol()
let key2: symbol = Symbol()
const obj = {
	[key1]: 'key1 value',
  [key2]: 'key2 value'
}
console.log(obj[key1]) // key1 value
console.log(obj[key2]) // key2 value

(2)作为属性名

因为symbol表示的值是独一无二的,所以可以当做属性名,并且不会与其他任何属性名重复,当需要访问这个属性时,只能使用这个symbol值来访问(必须使用方括号形式来访问

let symName = Symbol()
let syObj = {
    [symName]: 'Typescript'
}
console.log(syObj) // { [Symbol()]: 'Typescript' }
console.log(syObj[symName]) //Typescript
console.log(syObj.symName) // 'symName' does not exist on type '{ [x: symbol]: string; }'.

在使用 syObj.symName 访问时,实际上是在访问字符串 symName,这和访问普通字符串类型的属性名是一样的,要想访问属性名为symbol类型的属性时,必须使用方括号

(3) symbol属性名遍历

使用 symbol类型值作为属性名时,这个属性不会被 for...in 、Object.keys()、Object.getOwnPropertyNames()、JSON.stringify() 等方法获取到:

let symName = Symbol()
let syObj = {
    [symName]: 'Typescript',
    age: 18
}
console.log(Object.keys(syObj))  // [ 'age' ]
console.log(Object.getOwnPropertyNames(syObj))  //[ 'age' ]
console.log(JSON.stringify(syObj)) // {"age":18}

可以使用 Object.getOwnPropertySymbols() 获取symbol类型的值:

let symName = Symbol("name")
let syObj = {
    [symName]: 'Typescript',
    age: 18
}
console.log(Object.keys(syObj))  // [ 'age' ]
console.log(Object.getOwnPropertyNames(syObj))  //[ 'age' ]
console.log(JSON.stringify(syObj)) // {"age":18}
console.log(Object.getOwnPropertySymbols(syObj)) // [ Symbol(name) ]

除此之外,也可以使用ES6 提供的Reflect 对象的静态方法Reflect.ownKeys,他可以返回所以类型的属性名:

let symName = Symbol("name")
let syObj = {
    [symName]: 'Typescript',
    age: 18
}
console.log(Reflect.ownKeys(syObj)) // [ 'age', Symbol(name) ]

二、复杂数据类型

JavaScript复杂数据类型Typescript 复杂数据类型
Arrayarray
Objectobject
Functionfunction
元组
枚举
void
never
unknown

1.array

有两种方式可以定义数组。 第一种,可以在元素类型后面接上 [],表示由此类型元素组成的一个数组:

let list: number[] = [1, 2, 3];

第二种方式是使用数组泛型,Array<元素类型>:

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

推荐使用第一种方法,可以减少代码量。数组元素类型可以使用联合类型:

let list: number|string[] = [1, 2, 3, 'age']

2.object

let obj1: object
obj1 = {
    name: 'Typescript'
}
obj1 = 123 // 不能将类型“number”分配给类型“object”。ts(2322)

3.元组

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。 比如,你可以定义一对值分别为 string和number类型的元组。

// Declare a tuple type
let x: [string, number];
x = ['hello', 10]; // OK
x = [10, 'hello']; // Error

在新版本中,对元组做了越界判断,超出规定个数的元素称为越界元素,元素赋值必须类型和个数都对应。

interface Tuple extends Array<number | string> { 
   0: string; 
   1: number;
   length: 2; 
}

表示数组元素的类型必须是 string 或者 number,且length 必须为 2,多了或者少了都不行。

4.枚举

使用枚举类型可以为一组数值赋予友好的名字。

enum Color {Red, Green, Blue}
let c: Color = Color.Green;

默认情况下,从0开始为元素编号。 你也可以手动的指定成员的数值。 例如,我们将上面的例子改成从 1开始编号:

enum Color {Red = 1, Green, Blue}
let c: Color = Color.Green;

或者,全部都采用手动赋值:

enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;

枚举类型提供的一个便利是你可以由枚举的值得到它的名字。 例如,我们知道数值为2,但是不确定它映射到Color里的哪个名字,我们可以查找相应的名字:

enum Color {Red = 1, Green, Blue}
let colorName: string = Color[2];

console.log(colorName);  // 显示'Green'因为上面代码里它的值是2

5.any

any 表示任意类型,当我们在编码时不清楚一个值得类型的时候就可以用any类型了,对于声明为 any 类型的值可以对其做任何操作,包括获取他本身并不存在的属性、方法,因为Typescript 无法检测其属性是否存在、类型是否正确

let value: any;
value = 123;
value = "abc";
value = false;

const array: any[] = [1, "a", true];

6.void

void类型像是与any类型相反,它表示没有任何类型。 当一个函数没有返回值时,你通常会见到其返回值类型是 void:

function warnUser(): void {
    console.log("This is my warning message");
}

void 类型的变量只能赋值为 undefined 和 null,其他类型都不能赋值给void类型的变量。

7.never

never类型表示的是那些永不存在的值的类型。 例如, never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型; 变量也可能是 never类型,当它们被永不为真的类型保护所约束时。

never类型是任何类型的子类型,也可以赋值给任何类型;然而,没有类型是never的子类型或可以赋值给never类型(除了never本身之外)。 即使 any也不可以赋值给never。

下面是一些返回never类型的函数:

// 返回never的函数必须存在无法达到的终点
function error(message: string): never {
    throw new Error(message); // 抛出异常
}

// 推断的返回值类型为never
function fail() {
    return error("Something failed");
}

// 返回never的函数必须存在无法达到的终点
function infiniteLoop(): never {
    while (true) { // 死循环
    }
}

8.unknown

unknown 是 Typescript 在3.0 版本新增的类型,主要用来描述类型并不确定的变量,他和any的很相似,但还是有区别的:

let value: any
console.log(value.name)
console.log(value.toFixed())
console.log(value.length)

上面这样写都不会报错,因为是any类型,当 value 为 object 时访问name属性没有问题,当value 是数值类型时,访问toFixed方法也是没有问题的,当value是字符串或者数组时,读取他的 length属性也没有问题。

当值为 unknown 类型时,如果没有缩小范围的话,是不能对他进行任何操作的。

function getValue(value: unknown): string {
  if (value instanceof Date) { 
    return value.toISOString();
  }
  return String(value);
}

这里对value 的类型缩小为了 Date实例范围,所以进行了 value.toISOString()

unknown使用注意以下几点:

(1)任何类型的值都可以赋值给 unknown 类型

let value1: unknown;
value1 = "a";
value1 = 123;

(2)在没有将类型缩小之前是不能进行任何操作的

let value4: unknown;
value4 += 1; // error 对象的类型为 "unknown"

(3)unknown 类型不可以赋值给其他类型,只能赋值给 unknown 和any 类型。

let value2: unknown;
let value3: string = value2; // error 不能将类型“unknown”分配给类型“string”
value1 = value2;

(4)unknown 类型的值不能访问其属性、也不能作为函数或者作为类创建实例

let value5: unknown;
value5.age;   // error
value5();     // error
new value5(); // error

在实际使用中,如果遇到类型无法确定的情况,首先考虑使用 unknown,尽量避免使用any。

let marbried: boolean = false;
let age:number = 0;
let wname:string = 'kk';
// 下面两种等价
let arr1:number[] = [1,2,3];
let arr2: Array<number> = [1,2,3];

// 元组类型tuple,数量和类型已知的数组
let tuple1: [string, number] = ['hhh', 2]

// 枚举类型
enum Gender{
    GIRE,
    BOY
}
// console.log(Gender.BOY)
console.log(Gender['BOY'], Gender[1]);
console.log(Gender['GIRE'], Gender[0])

// 常量枚举
const enum Colors{  // 编译后这部分就没了
    RED, YELLOW, GREEN
}

let myColor = [Colors.RED, Colors.YELLOW, Colors.GREEN]

// 任意类型 any,如果变量定义为any 类型就跟js 差不多,不会进行类型检查
let root:any = document.getElementById('root')
// 内置类型 HTMLElement
let element: HTMLElement|null = document.getElementById('root')
// 非空断言 !
element!.style.color = 'red'
// null undefined 是其他类型的子类
// 如果"strictNullChecks": true, 就不能把 null undefined 赋值给 x
let x:number;
x = 1;
// x = undefined;
// x = null;

// never 代表不会出现的值
// 1.作为不会返回的函数的返回值,类型
function error(message:string):never{
    console.log('ok')
    throw new Error('报错了') // 直接结束了
}

function loop():never{
    while(true){ // 死循环了

    }
}

function fn(x: number | string){
    if(typeof x === 'number'){
        console.log(x)
    }else if(typeof x === 'string'){
        console.log(x)
    }else{
        console.log(x) // 永远不可能到达这儿,never
    }
}
// void 代表没有任何返回值,在非严格模式下 可以赋值给 null undefined,但是 never 不能包含任何类型
function grett():void{
    // return null
}
// 返回void 的函数能正常执行,返回 never 的函数无法正常执行,要么抛出错误,要么死循环

// Symbol 代表唯一不变的值

const s1 = Symbol('key')
const s2 = Symbol('key')

//BigInt ,和number 不兼容
// const max = Number.MAX_SAFE_INTEGER
const max = BigInt(Number.MAX_SAFE_INTEGER)
// 类型推导
let uname; // 初始没有赋值就是 any 类型,赋值给任何类型都可以
uname = 1;
uname = 'lal'

let uname2 = 'string';  // 可以推导出 uname2 是 string 类型
// uname2 = true // 此时会报错 Type 'boolean' is not assignable to type 'string'.ts(2322)

// 包装对象 wrapper object  会将原始类型自动包装成对象类型,然后就可以调用对应的方法了
let name2 = 'lz'
name2.toUpperCase() //name2 是个string 类型,原本没有toUperCase方法,但是内部会自动将string 类型包装成对象类型 new String(name2).toUpperCase()

let isOk1:boolean = true;
let isOk2:boolean = Boolean(1)
// let isOk3:boolean = new Boolean(1) // 不能将一个包装对象赋值给 boolean 类型
// 元组 表示一个已知元素数量和类型的数组,各元素的类型不必相同。
let x: [string, number];
// Initialize it
x = ['hello', 10]; // OK
// Initialize it incorrectly
x = [10, 'hello']; // Error

// 联合类型
let name3 : string | number
// 没赋值之前只能使用string 和 number 的共有方法
console.log(name3!.toString())  // !. 在变量名后添加,可以断言排除undefined和null类型
// 赋值给number 就可以使用 number 类型的方法
name3 = 3;
console.log(name3.toFixed())
// 赋值给 string 就可以使用 string 的方法
name3 = 'jg'
console.log(name3.length)

// 类型断言
let name4:string|number;
console.log((name4! as number).toFixed()) //  强行断言
console.log((name4! as string).length)
// 双重断言
console.log((name4! as any as boolean)) // 不能直接断言成boolean,可以先断言成any 然后再断言成Boolean

// 字面量类型和类型字面量
const up:'Up' = 'Up';
const down: 'Down' = 'Down'
const left: 'Left' = 'Left'
const right: 'Right' = 'Right'
type Direction = 'Up' | 'Down' | 'Left' | 'Right'
function move(direction:Direction){

}
move('Down')
// 类型字面量
type Person = {
    name: string, 
    age: number
}
let p1:Person ={
    name: 'na',
    age: 10,
}

// 字符串字面量和联合类型
type T1 = '1'|'2'|'3';
type T2 = string | number | boolean 
let t1:T1 = '2';
let t2:T2 = true
// 约束函数参数和返回值类型
function hello(name: string):void{ // 没有返回值就是 void
    console.log(name)
}
hello('yf')
// 定义函数类型
type GetName = (firstName: string, lastName: string) => string
function GetName(firstName: string, lastName: string): string{
    return firstName+ lastName
}
// 可选参数age
function print(name: string, age?:number):void{
    console.log(name, age)
}
//默认参数
function ajax(url: string, method: string = 'GET'){
    console.log(url, method)
}
ajax('/')
// 剩余参数
function sum( ...numbers: number[]){
    return numbers.reduce((val, item) => val+item, 0)
}
// 函数的重载
let obj:any = {}
// 定义函数 val 可以是string 也可以是 number
function attr(val: string):void
function attr(val: number):void
// 实现函数
function attr(val: any):void{
    if(typeof val === 'string'){
        obj.name = val;
    }else if(typeof val === 'number'){
        obj.age = val;
    }
}
// 这种也可以
function attr2(val: string|number):void{

}

// 这种就实现不了了
function add(a: string, b:string):void
function add(a: number, b: number):void
function add(a: string | number, b: string | number):void{

}

add(1, 2)
add('a', 'b')
// add(1, 'b') // 在上面的约束下这种就不符合了