TypeScript 简明教程:基本类型(一)

3,169 阅读8分钟

本文为系列文章《TypeScript 简明教程》中的一篇。

  1. 认识 TypeScript
  2. 安装 TypeScript
  3. 基本类型(一)

从这篇文章,我们开始讲解 TypeScript 的类型系统。之前我们说到,TypeScript 是 JavaScript 的超集,是 JavaScript 的继承与发展,即所谓的『增强版』。这一点,从 TypeScript 中的基本类型可以看出。TS 的数据类型与 JS 几乎相同,此外还额外提供一些数据类型方便我们使用。

首先,我们来看 JavaScript 中 6 种原始数据类型在 TypeScript 中的使用,它们分别为:numberstringbooleannullundefinedsymbol

原始数据类型

number 类型

TypeScript 中,使用 number 表示变量为数值类型。与 JavaScript 一致,TypeScript 中所有数值都是浮点数,支持二进制、八进制、十进制和十六进制四种表示方法。

let decimalNumber: number = 42
let hexNumber: number = 0x2A
let binaryNumber: number = 0b101010
let octalNumber: number = 0o52

二进制和八进制表示采用 ES6 新的写法,分别使用前缀 `0b`(或 `0B`)和 `0o`(或 `0O` 英文字母 O)表示。从 ES5 开始,不再允许使用前缀 `0` 表示八进制。如果 `tsconfig` 中 target 高于 ES5 版本,TypeScript 将会出现提示:禁止使用前缀 `0` 表示八进制。

除了浮点数以外,JavaScript 中还有一组特别的数值:Infinity-InfinityNaN,它们也属于 number 类型。

const positiveLargestNumber: number = Infinity
const negativeLargestNumber: number = -Infinity

const notANumber: number = NaN

Infinity 即正无穷,用以表示超出 JS 所能表示的最大值的数值,不同环境下这个值有所不同,可通过 Number.MAX_VALUE 查看,一般为 1.7976931348623157E+308。与之相对应的是 -Infinity,表示负无穷。

注意区分**负无穷**和**无穷小**两个概念。负无穷表示比任何数都小的一个负数值,无穷小表示以 0 为极限,无限趋于零的一个正数值。JS 中的无穷小值可通过 `Number.MIN_VALUE` 查看,一般为 `5E-324`。

NaN 表示一个本应返回数值的操作数未返回数值的情况。比如:

let newNumber: number = Number('a') // => NaN

更多内容可以参考:ECMAScript 6 入门 - 数值的扩展

string 类型

和其他编程语言一样,字符串是必不可少的一种类型,用于表示文本数据。可以通过 string 声明某个变量为字符串类型。

const theAnswer: number = 42
let sentence: string = `The Answer to the Ultimate Question of Life, The Universe, and Everything is ${ theAnswer }.`

TypeScript 中你依旧可以使用双引号(")和单引号(')来表示字符串。除此以外,你还可以使用 ES6 提供的 模板字符串 语法。TypeScript 会自动将其编译为字符串拼接的形式。

// 编译结果
var theAnswer = 42;
var sentence = "The Answer to the Ultimate Question of Life, The Universe, and Everything is " + theAnswer + ".";

boolean 类型

布尔值可以说是最基本的数据类型了,它的可能取值只有两个:truefalse。TypeScript 中使用 boolean 表示布尔值类型。

let num: number = -10
let isPositive: boolean = num > 0

symbol 类型

symbol 是 ES6 新增的数据类型,用于表示独一无二的值,只能 Symbol 函数生成。更多内容可以参考:ECMAScript 6 入门 - Symbol

const sym: symbol = Symbol()

事实上,TypeScript 官网上并没有详细介绍 symbol 这一基础数据类型。想来原因有二:

  1. symbol 值只能通过 Symbol() 函数生成,由于类型推断(后面会讲)的存在,无需特别声明变量为 symbol 类型。
  2. symbol 的唯一性依赖于底层实现,不太好做 polyfill,所以对于编译 target 低于 ES6 版本的情况,并不推荐使用 symbol

不过,出于完整性的考虑,这里还是简要提一下。

undefinednull 类型

JavaScript 中,undefinednull 都被用来表示空值。作为两个原始数据类型,undefined 类型的变量只能被赋值为 undefinednull 类型的变量只能被赋值为 null

// 一般来说,直接定义一个 undefined 或者 null 类型的变量并没有太大意义
let u: undefined = undefined
let n: null = null

默认情况下,undefinednull 是所有其他类型的子类型。也就是说,如果 undefinednull 赋值给任何其他类型的变量。

例如:

let person: string = 'Jerry'
person = null // 有效的

然而,如果在 tsconfig 中开启了 strictNullChecks,那么 undefinednull 就只能赋值给 voidany 类型变量以及它们自身类型的变量。强烈建议开启这一选项,它能帮助避免很多常见的问题

undefinednull 的关系

尽管 undefinednull 都表示空值,但是它们本质上是不同的。undefined 表示变量已声明但未初始化null 则在逻辑上表示一个空对象指针(这也正是 typeof null === 'object' 的原因)。

无论什么情况下,都没有必要把一个变量的值显式地设置为 undefined,可是同样的规则对 null 却不适用。换句话说,只要意在保存对象的变量还没有真正保存对象,就应该明确地让该变量保存 null 值。 —— Nicholas C.Zakas 《JavaScript 高级程序设计》

高级类型

void 类型

void 类型表示没有任何类型,这句话听起来可能有些自相矛盾。不过,让我们来考虑这样一种情况:如果一个函数没有返回值,我们如何表示其类型?这时候 void 类型就派上用场了。这里和 C 语言很像。

function warning(): void {
    console.log('WARNING!')
}

当然,这里你也可以指定返回值类型为 undefined。因为 JS 中,如果函数没有返回值,则会默认返回 undefind。不过,使用 void 类型可以使表意更清晰。

然而,声明一个 void 类型的变量并没有太大的用处,因为你只能将其赋值为 nullundefined

请注意与 void 运算符区分void 运算符的作用是:对给定的表达式进行求值,然后返回 undefined。如果你从事前端多年,你或许会比较熟悉 javascript:void(0); 这样的代码。感兴趣的可以看 这里

any 类型

某种程度上,any 类型就像是 void 类型的反面,任何类型都是 any 类型的子类型。换句话说,any 类型的变量可以被赋予任何类型的值。

let anything: any = "Ohh, that's crazy"
anything = 42

对于 any 类型,有两个需要注意的点。

首先,如果变量在声明时,没有声明其类型,也没有初始化(因为类型推断会自动判断类型),那么它会被当做 any 类型。

let something   // 等价于 let something: any;
something = 'autochess'
something = 6

其次,在 any 类型变量上可以访问任何属性,即使它并不存在。

let something: any = 42
something.mayExist()    // 没问题,因为其可能在运行时存在
something.toFixed() // 没问题,虽然确实存在,但是编译器并不会去检查

事实上,any 类型是 TypeScript 提供的一个『跳出』方案。对于 any 类型变量,编译器不会做任何类型检查,直接让它们通过类型检查。这在引入第三方库以及将原有 JS 代码改写为 TS 时尤其有用。

不过,注意不要滥用 any 类型。错误地滥用 any 类型会让 TS 失去其存在的意义。

never 类型

never 用于表示永远不会存在的值的类型。never 是任何类型的子类型,但没有类型是 never 的子类型。

对于 never 类型,新手理解起来可能比较困难。但是对于 TypeScript 这样一个分析代码流的语言来说,never 类型是理所当然且必要的。

never 类型常用于两种情况:

  1. 用于描述从不会有返回值的函数
  2. 用于描述总是抛出错误的函数
// 第一种情况
function neverStop(): never {
    while (true) {
        // do something
    }
}

// 第二种情况
function error(message: string): never {
    throw new Error(message)
}

对于上述两种情况,函数的返回类型都是 never

never 类型也可以用做变量的类型注解(例如:let foo: never;),但是这样做并没有什么意义。

下面我们通过一个例子来学习 never 类型的用法:

function checkNumber(x: string | number): boolean {
    if (typeof x === 'number') {
        return true
    } else if (typeof x === 'string') {
        return false
    }
    
    return fail('Failure')
}

function fail(message: string): never {
    throw new Error(message)
}

对于上面的 checkNumber 函数,如果没有 return fail('Failure') 这一句的话,TypeScript 会报错,因为不是所有条件语句都有返回值,当参数 x 既不是 number 也不是 string 时,函数会默认返回 undefined,然而在严格模式下,undefined 类型与 boolean 类型并不兼容。但是由于 fail 函数返回的 never 类型是 boolean 的子类型,所有上述写法可以通过编译。这也是 never 必须是任何类型的子类型的原因。

关于 never 类型,这里只是举个例子帮助大家理解。想要真正理解 never 类型,还得结合实战仔细揣摩才行。

总结

本篇文章主要介绍了 TypeScript 中的原始数据类型和几个高级类型。通过这篇文章,相信大家已经可以看出 TypeScript 类型系统的强大和完备。

当然这还不是全部,下篇文章我们将介绍 TypeScript 中其他几个高级类型:ObjectArrayTupleEnum。敬请期待~