许多编程语言都有一个称为null的非值 。它指示一个变量当前不指向一个对象,例如,当它还没有初始化的时候 。
相比之下,JavaScript有两个这样的非值:undefined和null 。在这篇博文中,我们将研究它们有什么不同,以及如何最好地使用或避免它们 。
1. undefined vs. null 这两种值非常相似,经常互换使用 。因此,它们的区别是微妙的 。
1.1 ECMAScript语言规范:undefined vs. null ECMAScript语言规范对其描述如下 。
- undefined是 "当一个变量没有被赋值时使用"
- null "表示有意不存在任何对象值"
那为什么不从 JavaScript 中删除其中的一个值呢?JavaScript 的一个核心原则是绝不破坏向后兼容 。这个原则有很多好处 。它最大的缺点是,设计上的错误无法被删除 。
1.3 undefined和null的历史 在Java中(它启发了JavaScript的许多方面),初始化值取决于变量的静态类型 。
- 具有对象类型的变量被初始化为null 。
- 每个原始类型都有自己的初始化值 。例如,int变量被初始化为0 。
undefined 的出现 如果一个变量myVar还没有被初始化,它的值是undefined 。
let myVar;assert.equal(myVar, undefined); 如果一个属性.unknownProp丢失,访问该属性会产生undefined的值 。
const obj = {};assert.equal(obj.unknownProp, undefined); 如果一个函数有一个没有参数的返回语句,该函数隐含地返回undefined 。
function myFunc() { return;}assert.equal(myFunc(), undefined); 如果一个参数x被省略了,语言将该参数初始化为undefined 。
function myFunc() { return;}assert.equal(myFunc(), undefined);myFunc(); 通过obj?.someProp的可选链路,如果obj未定义或为空,则返回未定义 。
> undefined?.somePropundefined> null?.somePropundefined 3.null 出现情况 一个对象的原型要么是一个对象,要么在原型链的末端是null 。Object.prototype没有原型 。
> Object.getPrototypeOf(Object.prototype)null 如果我们将一个正则表达式(如/a/)与一个字符串(如'x')相匹配,我们要么得到一个带有匹配数据的对象(如果匹配成功),要么得到空(如果匹配失败) 。
> /a/.exec('x')null JSON数据格式不支持undefined,只支持null 。
> JSON.stringify({a: undefined, b: null})'{"b":null}' 4.特别处理 undefined 或 null 的运算符 4.1 undefined 和参数默认值 在以下情况下,将使用参数默认值 。
- 缺少一个参数 。
- 一个参数的值是undefined 。
function myFunc(arg='abc') { return arg;}assert.equal(myFunc('hello'), 'hello');assert.equal(myFunc(), 'abc');assert.equal(myFunc(undefined), 'abc'); undefined也触发了参数的默认值,这说明它是一个元值 。
下面的例子说明了这一点的用处 。
function concat(str1='', str2='') { return str1 + str2;}function twice(str) { // (A) return concat(str, str);} 在A行,我们没有为str指定一个参数默认值 。当这个参数缺失时,我们把这个状态转发给concat(),让它选择一个默认值 。
4.2 undefined 的和解构的默认值 解构中的缺省值与参数缺省值的工作原理类似--如果一个变量在数据中没有匹配,或者它与undefined的变量匹配,就会使用它们 。
const [a='a'] = [];assert.equal(a, 'a');const [b='b'] = [undefined];assert.equal(b, 'b');const {prop: c='c'} = {};assert.equal(c, 'c');const {prop: d='d'} = {prop: undefined};assert.equal(d, 'd'); 4.3 undefined 和 null 以及可选链 当通过 value?.prop 进行可选的链接时
- 如果value是undefined或null,则返回 undefined 。也就是说,只要value.prop会抛出一个异常,就会发生这种情况 。
- 否则,返回value.prop 。
obj?.[?expr?] // optional dynamic property accessfunc?.(?arg0?, ?arg1?) // optional function or method call 4.4 undefined 和 null 和 nullish 聚结 nullish 合并算子??如果一个值undefined或为null,则允许我们使用默认值:
> undefined ?? 'default value''default value'> null ?? 'default value''default value'> 0 ?? 'default value'0> 123 ?? 'default value'123> '' ?? 'default value'''> 'abc' ?? 'default value''abc' null合并赋值操作符??=:
function setName(obj) { obj.name ??= '(Unnamed)'; return obj;}assert.deepEqual( setName({}), {name: '(Unnamed)'});assert.deepEqual( setName({name: undefined}), {name: '(Unnamed)'});assert.deepEqual( setName({name: null}), {name: '(Unnamed)'});assert.deepEqual( setName({name: 'Jane'}), {name: 'Jane'}); 5. 处理 undefined 和 null 下面分享一下我们自己的代码中处理undefined 和null的最常见方法 。
5.1 undefined 或 null 都不能作为实际值使用 举例,我们可能希望一个属性file.title永远存在,并且永远是一个字符串 。有两种常见的方法来实现这一点 。
这里,只检查undefined和null,而不检查一个值是否是字符串 。你必须自己决定是否要把它作为一个额外的安全措施来实施 。
5.1 undefined 和 null 都是禁止的 如下所示
function createFile(title) { if (title === undefined || title === null) { throw new Error('`title` must not be nullish'); } // ···} 为什么选择这种方法?
我们想把undefined和null当作一样的东西,因为JavaScript代码经常这样做--比如说 。
// 检测一个属性是否存在if (!obj.requiredProp) { obj.requiredProp = 123;}const myValue = myParameter ?? 'some default'; 如果我们的代码中出现了问题,出现了undefined或null,我们希望它尽可能快地失败 。
5.1.2 undefined 和 null 都会触发默认值 如下所示
function createFile(title) { title ??= '(Untitled)'; // ···} 我们不能在这里使用参数缺省值,因为它只由undefined 触发 。相反,我们依靠nullish 合并赋值运算符??= 。
为什么选择这种方法?
- 我们想把undefined和null同等对待(见上一节) 。
- 我们希望我们的代码能够稳健地、无声地处理 undefined 和null 。
5.2.1 null是 "关闭的 "值 function createFile(title) { if (title === undefined) { throw new Error('`title` must not be undefined'); } return {title};} 另外,undefined可以触发一个默认值 。
function createFile(title = '(Untitled)') { return {title};} 为什么选择这种方法?
- 我们需要一个表示“关闭”的非值 。
- 我们不希望我们的非值触发参数默认值和解构默认值 。
- 我们想把非值字符串化为JSON(这是我们不能用undefined做的) 。
- 我们需要一个表示“关闭”的非值 。
- 我们确实想让我们的非值触发参数默认值和解构默认值 。
这指向了一种不同的方法 。如果我们需要一个 "关闭"的值,但又不想使用undefined或null作为这样的值,怎么办?请往下看 。
5.3 处理“关闭”的其他方式 5.3.1 特殊值 我们可以创建一个特殊的值,每当属性.title被关闭时,我们就使用这个值 。
const UNTITLED = Symbol('UNTITLED');const file = { title: UNTITLED,}; 5.3.2 空对象模式 空对象模式来自于面向对象的编程 。
- 一个共同的超类的所有子类都有相同的接口 。
- 每个子类都实现了一个实例运行的不同模式 。
- 其中一个模式是 "null" 。
// 抽象的超类class File { constructor(content) { if (new.target === File) { throw new Error('Can’t instantiate this class'); } this.content = content; }}class TitledFile extends File { constructor(content, title) { super(content); this.title = title; } getTitle() { return this.title; }}class UntitledFile extends File { constructor(content) { super(content); } getTitle() { return '(Untitled)'; }}const files = [ new TitledFile('Dear diary!', 'My Diary'), new UntitledFile('Reminder: pick a title!'),];assert.deepEqual( files.map(f => f.getTitle()), [ 'My Diary', '(Untitled)', ]); 我们也可以只对标题使用空对象模式(而不是对整个文件对象) 。
5.3.3 Maybe 类型 Maybe 类型是一种函数编程技术 。
function getTitle(file) { switch (file.title.kind) { case 'just': return file.title.value; case 'nothing': return '(Untitled)'; default: throw new Error(); }}const files = [ { title: {kind: 'just', value: 'My Diary'}, content: 'Dear diary!', }, { title: {kind: 'nothing'}, content: 'Reminder: pick a title!', },];assert.deepEqual( files.map(f => getTitle(f)), [ 'My Diary', '(Untitled)', ]); 我们可以通过Arrays对 "just "和 "nothing "进行编码 。我们的方法的好处是,它得到了TypeScript的良好支持(通过判别性联合) 。
6.我的方法 我不喜欢用undefined作为 "关闭 "的值,有三个原因 。
- undefined经常意外地出现在JavaScript中 。
- ndefined会触发参数和结构化的默认值(出于同样的原因,有些人喜欢undefined) 。
- 我使用null作为一个 "关闭 "的值 。(作为一个旁观者,这种方法在TypeScript中得到了比较好的支持) 。)
【5年前学习null和undefined undefined】我通过上述的技术之一来避免undefined和null 。这样做的好处是比较干净,坏处是涉及更多的工作 。
推荐阅读
- 2022几月几日是父亲节? 父亲节是几月几日
- 上门取件退货,拼多多为何只用顺丰? 顺丰快递上门取件
- 生日发朋友圈怎样写好
- 整个孕期要抽几次血
- 保胎期间可以吃黄鳝虾粥吗
- 15周产前诊断检查什么
- 孕妇能吃狼牙土豆吗
- 怀孕可以吃腌的蒜头吗
- 大排畸25周做可以吗
