TypeScript null 与 undefined

在实际的业务开发中,我们经常会遇到 “数据不存在” 或者 “数据还没拿到” 的情况。为了表示这种 “无” 的状态,JavaScript 提供了两个特殊的值:undefined 和 null。

null 与 undefined 之间有什么区别?

很多初学的小伙伴容易把 undefinednull 混为一谈,因为它们在条件判断时都会被当作 false。但实际上,它们的业务语义是完全不同的:

  • undefined(未定义值):表示一个变量已经声明了,但还没有被赋任何值。它就像是你在考场上留着没写的空白答题卡。
  • null(空值):表示一个变量被明确地赋值为空。它就像是你不仅交了白卷,还在答题卡上特意写了一句 “我不会做”。

undefined vs null

TypeScript 的严格模式

在 TypeScript 中,我们同样可以使用 undefined 和 null 来定义变量。

示例 1:基本使用

// 声明一个 undefined 类型的变量,它只能被赋值为 undefined
let u: undefined = undefined;

// 声明一个 null 类型的变量,它只能被赋值为 null
let n: null = null;

console.log(u);
console.log(n);

运行结果如下。

undefined
null

分析:

在传统的 JavaScript 中,null 和 undefined 是非常随性的,我们可以把它们随意赋值给字符串或数字变量。

但在 TypeScript 的现代企业级项目(比如 Vue 或 React)中,我们都会默认在 “配置文件 tsconfig.json” 中开启一个非常重要的选项:"strictNullChecks": true(严格空值检查)。

在这个严格模式下,null 和 undefined 拥有了自己独立的类型。因此我们绝对不能把它们强行赋值给其他类型的变量。

示例 2:严格模式的拦截

// 假设tsconfig.json 中开启了 strictNullChecks: true

let siteName: string = "绿叶网";
// 错误操作:试图把 null 赋值给字符串变量
siteName = null;

分析:

一旦我们写出上面的代码,那么 TypeScript 就会直接报错拦截:

不能将类型 “null” 分配给类型 “string

TypeScript 这种严苛的限制,能够从根本上杜绝大量因为 “数据为空” 而导致的页面白屏或程序崩溃问题。

此外,有小伙伴可能会问:“在真实业务中,有些数据刚开始确实是空的(比如还没从后端请求回来的用户名),我应该如何定义它呢?”

在 TypeScript 中,如果我们希望一个变量既能是字符串,又能是 null,此时可以使用一个竖线 “|” 把它们连起来,这叫做 “联合类型”(下一章我们会详细介绍):

// 告诉 TypeScript:siteName 可以是字符串,也可以是 null
let siteName: string | null = "绿叶网";

// 此时赋值为 null 是完全合法的,不会报错
siteName = null;

TypeScript 经典报错:对象可能为 "null"

了解了严格模式之后,我们就不得不面对初学者最常遇到的一个 “噩梦级” 报错。

假设在页面里有一个 id 为 "title" 的 HTML 标签。然后我们想要通过 TypeScript 拿到它,并打印里面的文字。

示例 3:防不胜防的报错

// 获取页面上的 DOM 元素
const titleElement = document.getElementById("title");

// 直接访问它的属性,TypeScript 直接飘红报错
console.log(titleElement.innerText);

分析:

为什么会报错呢?这是因为 TypeScript 是非常严谨的,它在编译时无法确定你的 HTML 页面里到底有没有 <div id="title"></div> 这个标签。

万一页面里根本没这个元素,那么 document.getElementById() 的返回值就会是 null。如果我们直接对 null 读取 innerText 属性,程序就会当场崩溃。

为了解决这个报错,我们有以下 3 种处理方案:

1. 使用类型收窄(强烈推荐)

这是一种最稳妥的企业级写法。我们先使用 if 语句判断,只有当它不是 null 的时候,才执行内部代码。

const titleElement = document.getElementById("title");

// 类型收窄
if (titleElement !== null) {
    console.log(titleElement.innerText);
}

2. 使用可选链操作符 “?.”(最优雅)

这是 ES6+ 提供的新语法,非常的简洁。它的意思是:如果元素存在,就取它的 innerText;如果是 null 或 undefined,就直接返回 undefined,并且不再往下执行。

const titleElement = document.getElementById("title");

// 只有 titleElement 存在时,才会去读取 innerText
console.log(titleElement?.innerText);

3. 使用非空断言 “!”(谨慎使用)

如果我们能够百分之百确定页面上绝对有这个元素,此时可以使用 TypeScript 专属的 “非空断言(!)”。所谓的 “非空断言”,指的是在变量后面加个感叹号(!),强行让 TypeScript 闭嘴。

const titleElement = document.getElementById("title");

// 意思是:“我保证它绝对不是 null,你尽管执行”
console.log(titleElement!.innerText);

注意: 使用 “!” 虽然简单粗暴,但也破坏了类型安全。如果运行时元素真的不存在,程序依然会崩溃,因此建议大家少用。

TypeScript 还有其他基础类型吗?

到目前为止,我们已经学习了 TypeScript 中最核心的所有基础类型,包括:number、string、boolean、null、undefined、any、unknown、void、never 等。

如果小伙伴们对现代 JavaScript(ES6+)比较了解,可能了解到 JavaScript 还有两个特殊的原始数据类型:

  • symbol:用于创建全局唯一标识符。
  • bigint:用于表示超大整数)。

实际上,TypeScript 同样完美支持这两种类型:

// symbol
const sym: symbol = Symbol("唯一的key");

// bigint
const bigNum: bigint = 9007199254740991n;

不过由于 symbol 和 bigint 在实际开发中很少会用到,我们这里只需要简单了解即可。

上一篇: TypeScript void 与 never

下一篇: TypeScript 接口

给站长反馈

绿叶网正在不断完善中,小伙伴们如果发现任何问题,还望多多给站长反馈,谢谢!

邮箱:lvyenet@vip.qq.com

「绿叶网」服务号
绿叶网服务号放大
关注服务号,微信也能看教程。
绿叶网服务号