likes
comments
collection
share

Typescript学习(二) 基础类型

作者站长头像
站长
· 阅读数 21

原始类型

我们在Javascript当中, 原始类型有string, number, boolean, bigInt, symbol, null, undefined 其中, 前五种在Typescript类型系统当中, 有对应的类型注解, 就是其字面含义, 例如: string就是字符串类型, number就是数字类型; 这些很好理解, 也很单纯;

let str:string = '字符串'
let num:number = 100
let bool:boolean = true
let sym:symbol = Symbol('123')
let big:bigint = BigInt(123)

但是, undefined和null, 在typescript中, 除了能表示javascript中的空值没有值之外; 还有很多特殊的点需要了解, 因此, 此处首先对undefined和null展开学习:

undefined、null

首先, 在Typescript类型系统中, undefined/null和上面介绍的string/number等一样, 都是一个类型; 都可以拿来做类型签名

let undefinedData:undefined = undefined
let nullData:null = null

其次, strictNullChecks: false的情况下, 它们又是其他类型的子类型:

在Typescript中, A = B成立, 则说明, B是A的子类型

// 以下内容在strictNullChecks: false的时候成立
let undefinedString:string = undefined
let nullString:string = null

void

在Typescript中, 还有一种void类型, 在Javascript当中, void只能表示是一个运算符, 它能执行后面的表达式, 无论这个表达式的结果是什么, 其运算结果永远是undefined, 但是在Typescript类型系统中, 它还表示是一个空值类型, 用来表示没有明确return的函数的返回值的类型, 其实void在Typescript中的作用只是希望调用者不要去关心函数的返回值;

有几个和undefined和null混用的场景我们需要注意:

  • 当我们定义了一个函数, 又没有返回任何值的时候, 返回值类型会被隐式推导为void;
  • 如果strictNullChecks: true, 函数明确return了undefined/null, Typescript才会自动推导该函数返回值的类型为undefined/null类型, 而不是void! 如果strictNullChecks: false , 函数明确return了undefined/null, 则返回值类型会被推导为any, 此时需要设置noImplicitAny: false, 防止报错;
  • 如果已经为一个函数定义了返回类型为void, 则strictNullChecks:false情况下可以返回undefined/null; strictNullChecks:true情况下, 则只能返回undefined;

Typescript学习(二) 基础类型

Typescript学习(二) 基础类型

当然, 在typescript中, 虽然一个没有返回值的函数会被隐式推导为void, 但是, 实际执行函数得出的值还是undefined, 所以, undefined类型的数据, 其实在任何条件下, 都可以赋值给void, 这也符合javascript实际运行的特点

let undefinedData:void = undefined // 无论仅strictNullChecks为何, 都行
let nullData:void = null // 仅strictNullChecks:false时可以

数组类型

typescript 中的数组类型一般表达为

let arr:string[] = ['str']
let arr2:Array<string> = ['str']

很好理解, 就是类型[], 或者Array<类型>, 这两种形式, 但是, 在typescirpt中, 如果想让代码更具备可维护性, 我们可以使用元组(Tuple),所谓的元组, 其实就是类型化的数组; 限定了数组内成员的类型、位置和数组的长度

let arr:[string, number, boolean] = ['str', 1, true]

之所以说元组比普通数组更具备可维护性, 主要表现在:

  1. 可以防止越界访问, 一旦越界会提示错误, 越界访问, 可以说是我们日常开发中, 可能会犯的错误, 也可以通过元组来规避掉;

Typescript学习(二) 基础类型

  1. 在Typescript中, 我们通过?来定义可选属性, 当我们将元组某个设定元素为可选时, 该成员类型会被自动推导为 xxx | undefined , 多了一个undefined可以规避很多隐藏的风险, 比如: 元组中某个成员有可能不存在, 于是我们将其定义了可选属性, 而这个属性又是一个Function类型, 如果直接调用, 则会提示错误:

Typescript学习(二) 基础类型

  1. 和可选属性一样, 我们可以通过readonly来设置元组为只读, 日常开发中可能我们希望一个数组设置了, 就不再被改变, 此时, readonly就派上用场了, 一旦我们用了readonly, 数组的push等能改变原数组的方法将无法使用

Typescript学习(二) 基础类型

  1. 可以为元素命名

上面的案例中,我们不知道arr中三个元素的具体含义, 在Typescript中, 我们可以为每个数组成员设置一个名字, 就像设置对象那样!

我们可以为数组的每个元素赋予一个特殊的含义:

Typescript学习(二) 基础类型

注意了, 此时的len类型为2 | 3, 没错, 这就是它的类型! 这是数字字面量类型的联合类型, 这个后面会详细介绍;

对象类型

前面我们介绍了原始数据类型的类型标注和数组/元组类型的概念, 现在我们来看下对象的类型标注; 在Typescript中, 我们通常使用关键字interface声明对象的类型标注:

interface Student {
  readonly name: string,
  age: number,
  grade: number,
  scores: number,
  getScore?: Function
}
// 标注了Student的对象, 必须实现所有非可选属性
let student:Student = {
  name: 'jack',
  age: 12,
  grade: 1,
  scores: 100
}

对象的类型标注

  1. 可选属性, 和元组的可选属性一样, 如果我们没有实现一个可选的函数类型的属性, 且strictNullChecks:true时, 直接调用它, 一样会报错, 因为此时student.getScore的类型为 Function | undefined, 而undefined显然不能被执行

Typescript学习(二) 基础类型

如果我们在调用之前, 声明好这个方法, 则不会再提示错误

Typescript学习(二) 基础类型

此时funcType的类型就被自动推导为了Function类型;

这里有一个点需要注意, 无论我们一开始是否有给可选属性设置值, 它的类型都是 xxx | undefined, 而如果我们通过obj.prop = xx的形式赋值之后, 它的undefined类型就会被去掉! 这点对象/元组都一样! 接前面的例子, 假如我们一开始就给getScore属性赋值, 我们直接调用这个方法一样会报错:

Typescript学习(二) 基础类型

可以看到, 此时funcType, 仍然是 ****Function | undefined

  1. 除了可选属性外, 我们还可以使用readonly来修饰接口的属性, 被修饰的属性将不能被再次赋值

Typescript学习(二) 基础类型

这里需要注意的是, 对象是控制某个属性是否为只读, 而元组则是只能控制整个元组是否为只读!

Object的困惑

在Javascript中, 有Object, String, Number等装箱类型, 而在Typescript中, 它们同样存在, 但是, 和之前的很多案例一样, 既有和javascript相同的点, 又有不同点; 我们都知道, 在Javascript中, 我们可以调用基础类型上的方法

'12.12'.contains('12') // true

我们可以看到, '12.12'只是一个字符串是一个基础类型, 讲道理它不存在所谓的什么属性, 但是依然可以通过点操作符来调用一个方法, 其本质是经过了一个隐式'装箱'过程:

  1. 创建一个String的实例, let instance = new String('12.12');
  2. 调用这个实例上的方法, instance.contains('12');
  3. 销毁实例, instance = null;

以上是Javascript中, 装箱类型的概念, 而在Typescript当中, 装箱类型可能会引起一定的困惑, 尤其是Object, 注意首字母大写, 不是object! 我们知道, Javascript的原型链不断往上都能找到Object, 因为一切都是由它构造出来的, 所以, 在Typescript的类型系统中, 除了strictNullChecks: true下的undefined和null之外, 所有其他基础类型一样是它的子类型!

// 以下均成立!
let str:Object = 'this is string'
let num:Object = 12
let bool:Object = true
let Sym:Object = Symbol('123')
let Sym2:Object = BigInt(999999999)

// strictNullChecks: false下成立
let nullData:Object = null
let undefinedData:Object = undefined
let voidData:Object = void(123)

此外, 其他基础类型的构造器的原型对象, 均能被它们的装箱类型接受!尽管原型对象并非一个基础类型!

// 以下也都成立!
let strs:String = String.prototype
let str:String =  String.prototype
let nums:Number = Number.prototype
let bools:Boolean = Boolean.prototype

所以, 在Typescript中, 无论何种情况下, 我们不要使用装箱类型来作为类型标注, 否则, 程序将产生极大的类型漏洞; 我们应该使用object来作为对象的类型标注, 它只允许: 对象, 数组, 函数三种引用类型赋值; 基础类型则使用string, number, boolean等来表示;

还有一种很奇怪的类型签名: {}, 它和Object一样, 都是任意非undefined/null的值!

// 以下又是都成立的!
let str:{} = 'jack'
let num:{} = 123
let bool:{} = true
let sym:{} = Symbol('123')
let bigInt:{} = BigInt(123)

所以, 对于类型的使用, 我们可以得出如下结论:

  1. 当我们要表示一个非原始类型的变量时, 要使用object, 而非Object和{};
  2. 当我们要表示基础类型时, 不得使用装箱类型; 而要使用对应的类型签名;