5.3.6 Nullable
TypeScript中的Nullable类型指的是值可以为undefined或null的类型。
JavaScript中有两个比较特殊的原始类型,即Undefined类型和Null类型。两者分别仅包含一个原始值,即undefined值和null值,它们通常用来表示某个值还未进行初始化。
在TypeScript早期的版本中,没有提供与JavaScript中Undefined类型和Null类型相对应的类型。TypeScript允许将undefined值和null值赋值给任何其他类型。虽然在TypeScript语言的内部实现中确实存在这两种原始类型,但是之前没有将它们开放给开发者使用。
TypeScript 2.0版本的一个改变就是增加了undefined类型和null类型供开发者使用。虽然看上去是一项普通的改进,但却有着非凡的意义。因为,不当地使用undefined值和null值是程序缺陷的主要来源之一,并有可能导致价值亿万美元的错误。相信一定有不少读者都曾经遇到过如下的JavaScript程序错误:
TypeError: Cannot read property 'xxx' of undefined
现在,在TypeScript程序中能够明确地指定某个值的类型是否为undefined类型或null类型。TypeScript编译器也能够对代码进行更加细致的检查以找出程序中潜在的错误。
5.3.6.1 undefined
undefined类型只包含一个可能值,即undefined值。undefined类型使用undefined关键字标识。示例如下:
01 const foo: undefined = undefined;
5.3.6.2 null
null类型只包含一个可能值,即null值。null类型使用null关键字标识。示例如下:
01 const foo: null = null;
5.3.6.3 --strictNullChecks
TypeScript 2.0还增加了一个新的编译选项“--strictNullChecks”,即严格的null检查模式。虽然该编译选项的名字中只提及了null,但实际上它同时作用于undefined类型和null类型的类型检查。
在默认情况下,“--strictNullChecks”编译选项没有被启用。这时候,除尾端类型外的所有类型都是Nullable类型。也就是说,除尾端类型外所有类型都能够接受undefined值和null值。关于尾端类型的详细介绍请参考5.8节。
例如,在没有启用“--strictNullChecks”编译选项时,允许将undefined值和null值赋值给string类型等其他类型。示例如下:
01 /** 02 * --strictNullChecks=false 03 */ 04 let m1: boolean = undefined; 05 let m2: string = undefined; 06 let m3: number = undefined; 07 let m4: bigint = undefined; 08 let m5: symbol = undefined; 09 let m6: undefined = undefined; 10 let m7: null = undefined; 11 12 let n1: boolean = null; 13 let n2: string = null; 14 let n3: number = null; 15 let n4: bigint = null; 16 let n5: symbol = null; 17 let n6: undefined = null; 18 let n7: null = null;
该模式存在一个明显的问题,就是无法检查出空引用的错误。例如,已知某一个变量的类型是string,于是通过访问其length属性来获取该变量表示的字符串的长度。但如果string类型的变量值可以为undefined或null,那么这段代码在运行时将产生错误。示例如下:
01 /** 02 * --strictNullChecks=false 03 */ 04 let foo: string = undefined; // 正确,可以通过类型检查 05 06 foo.length; // 在运行时,将产生类型错误 07 08 // 运行结果: 09 // Error: TypeError: Cannot read property 'length' 10 // of undefined
此例中,将undefined值赋值给string类型的变量foo时不会产生编译错误。但是,在运行时尝试读取undefined值的length属性将产生类型错误。这个问题可以通过启用“--strictNullChecks”编译选项来避免。
当启用了“--strictNullChecks”编译选项时,undefined值和null值不再能够赋值给不相关的类型。例如,undefined值和null值不允许赋值给string类型。在该模式下,undefined值只能够赋值给undefined类型;同理,null值也只能赋值给null类型。
还是以上例中的代码为例,如果我们启用了“--strictNullChecks”编译选项,那么TypeScript编译器就能够检查出代码中的错误。示例如下:
01 /** 02 * --strictNullChecks=true 03 */ 04 let foo: string = undefined; 05 // ~~~ 06 // 编译错误!类型 'undefined' 不能赋值给类型 'string' 07 08 foo.length;
此例第4行,TypeScript在执行静态类型检查时就能够发现这处类型错误,从而避免了在代码运行时才发现这个缺陷。
前面我们说在启用了“--strictNullChecks”编译选项时,undefined值只能够赋值给undefined类型,null值只能够赋值给null类型,实际上这种表述不完全准确。因为在该模式下,undefined值和null值允许赋值给顶端类型,同时undefined值也允许赋值给void类型。这些类型在后面的章节中会有详细介绍。示例如下:
01 /** 02 * --strictNullChecks=true 03 */ 04 let m1: void = undefined; 05 06 let m2: any = undefined; 07 let m3: unknown = undefined; 08 09 let n2: any = null; 10 let n3: unknown = null;
undefined类型和null类型是不同的类型,它们必须被区分对待,不能互换使用。示例如下:
01 /** 02 * --strictNullChecks=true 03 */ 04 const foo: undefined = null; 05 // ~~~ 06 // 编译错误!类型 'null' 不能赋值给类型 'undefined' 07 08 const bar: null = undefined; 09 // ~~~ 10 // 编译错误!类型 'undefined' 不能赋值给类型 'null'
在了解了“--strictNullChecks”编译选项的作用后,让我们来看一看如何启用该编译选项。在默认情况下,“--strictNullChecks”编译选项没有被启用,我们需要在工程下的tsconfig.json配置文件中启用该编译选项,通过将“strictNullChecks”属性设置为“true”就能够启用“--strictNullChecks”编译选项。同理,如果将该属性设置为“false”则会关闭该编译选项。关于配置文件的详细介绍请参考8.3节。示例如下:
01 { 02 "compilerOptions": { 03 "strictNullChecks": true 04 } 05 }
如果读者使用的是TypeScript官网提供的在线代码编辑器,则可以在“Config”菜单中找到该选项并选中,即可启用该选项。
[1] Null类型的发明者Tony Hoare曾将Null描述为“billion-dollar mistake”。