5年前,学习 Null 和Undefined ,现在有了新的认知,看看这位人才怎么说?

开发 前端
JavaScript有两个这样的非值:undefined和null。在这篇博文中,我们将研究它们有什么不同,以及如何最好地使用或避免它们。

[[413681]]

许多编程语言都有一个称为null的非值。它指示一个变量当前不指向一个对象,例如,当它还没有初始化的时候。

相比之下,JavaScript有两个这样的非值:undefined和null。在这篇博文中,我们将研究它们有什么不同,以及如何最好地使用或避免它们。

1. undefined vs. null

这两种值非常相似,经常互换使用。因此,它们的区别是微妙的。

1.1 ECMAScript语言规范:undefined vs. null

ECMAScript语言规范对其描述如下。

  • undefined是 "当一个变量没有被赋值时使用"
  • null "表示有意不存在任何对象值"

1.2 两个非值--一个无法消除的错误

在JavaScript中拥有两个非值现在被认为是一个设计错误(甚至被JavaScript的创造者Brendan Eich认为)。

那为什么不从 JavaScript 中删除其中的一个值呢?JavaScript 的一个核心原则是绝不破坏向后兼容。这个原则有很多好处。它最大的缺点是,设计上的错误无法被删除。

1.3 undefined和null的历史

在Java中(它启发了JavaScript的许多方面),初始化值取决于变量的静态类型。

  • 具有对象类型的变量被初始化为null。
  • 每个原始类型都有自己的初始化值。例如,int变量被初始化为0。

在JavaScript中,每个变量都可以容纳对象值和基本类型值。因此,如果null意味着 "不是一个对象",那么JavaScript也需要一个初始化值,意味着 "既不是一个对象也不是一个原始值"。这个初始化值就是 undefined。

undefined 的出现

如果一个变量myVar还没有被初始化,它的值是undefined。

  1. let myVar; 
  2. assert.equal(myVar, undefined); 

如果一个属性.unknownProp丢失,访问该属性会产生undefined的值。

  1. const obj = {}; 
  2. assert.equal(obj.unknownProp, undefined); 

如果一个函数有一个没有参数的返回语句,该函数隐含地返回undefined。

  1. function myFunc() { 
  2.   return
  3. assert.equal(myFunc(), undefined); 

如果一个参数x被省略了,语言将该参数初始化为undefined。

  1. function myFunc() { 
  2.   return
  3. assert.equal(myFunc(), undefined); 
  4. myFunc(); 

通过obj?.someProp的可选链路,如果obj未定义或为空,则返回未定义。

  1. > undefined?.someProp 
  2. undefined 
  3. null?.someProp 
  4. undefined 

3.null 出现情况

一个对象的原型要么是一个对象,要么在原型链的末端是null。Object.prototype没有原型。

  1. > Object.getPrototypeOf(Object.prototype) 
  2. null 

如果我们将一个正则表达式(如/a/)与一个字符串(如'x')相匹配,我们要么得到一个带有匹配数据的对象(如果匹配成功),要么得到空(如果匹配失败)。

  1. > /a/.exec('x'
  2. null 

JSON数据格式不支持undefined,只支持null。

  1. > JSON.stringify({a: undefined, b: null}) 
  2. '{"b":null}' 

4.特别处理 undefined 或 null 的运算符

4.1 undefined 和参数默认值

在以下情况下,将使用参数默认值。

  • 缺少一个参数。
  • 一个参数的值是undefined。

例如:

  1. function myFunc(arg='abc') { 
  2.   return arg; 
  3. assert.equal(myFunc('hello'), 'hello'); 
  4. assert.equal(myFunc(), 'abc'); 
  5. assert.equal(myFunc(undefined), 'abc'); 

undefined也触发了参数的默认值,这说明它是一个元值。

下面的例子说明了这一点的用处。

  1. function concat(str1='', str2='') { 
  2.   return str1 + str2; 
  3. function twice(str) { // (A) 
  4.   return concat(str, str); 

在A行,我们没有为str指定一个参数默认值。当这个参数缺失时,我们把这个状态转发给concat(),让它选择一个默认值。

4.2 undefined 的和解构的默认值

解构中的缺省值与参数缺省值的工作原理类似--如果一个变量在数据中没有匹配,或者它与undefined的变量匹配,就会使用它们。

  1. const [a='a'] = []; 
  2. assert.equal(a, 'a'); 
  3.  
  4. const [b='b'] = [undefined]; 
  5. assert.equal(b, 'b'); 
  6.  
  7. const {prop: c='c'} = {}; 
  8. assert.equal(c, 'c'); 
  9.  
  10. const {prop: d='d'} = {prop: undefined}; 
  11. assert.equal(d, 'd'); 

4.3 undefined 和 null 以及可选链

当通过 value?.prop 进行可选的链接时

  • 如果value是undefined或null,则返回 undefined。也就是说,只要value.prop会抛出一个异常,就会发生这种情况。
  • 否则,返回value.prop。
  1. function getProp(value) { 
  2.   // optional static property access 
  3.   return value?.prop; 
  4. assert.equal( 
  5.   getProp({prop: 123}), 123); 
  6. assert.equal( 
  7.   getProp(undefined), undefined); 
  8. assert.equal( 
  9.   getProp(null), undefined); 

下面两个操作的效果类似。

  1. obj?.[«expr»] // optional dynamic property access 
  2. func?.(«arg0», «arg1») // optional function or method call 

4.4 undefined 和 null 和 nullish 聚结

nullish 合并算子??如果一个值undefined或为null,则允许我们使用默认值:

  1. > undefined ?? 'default value' 
  2. 'default value' 
  3. null ?? 'default value' 
  4. 'default value' 
  5.  
  6. > 0 ?? 'default value' 
  7. > 123 ?? 'default value' 
  8. 123 
  9. '' ?? 'default value' 
  10. '' 
  11. 'abc' ?? 'default value' 
  12. 'abc' 

null合并赋值操作符??=:

  1. function setName(obj) { 
  2.   obj.name ??= '(Unnamed)'
  3.   return obj; 
  4. assert.deepEqual( 
  5.   setName({}), 
  6.   {name'(Unnamed)'
  7. ); 
  8. assert.deepEqual( 
  9.   setName({name: undefined}), 
  10.   {name'(Unnamed)'
  11. ); 
  12. assert.deepEqual( 
  13.   setName({namenull}), 
  14.   {name'(Unnamed)'
  15. ); 
  16. assert.deepEqual( 
  17.   setName({name'Jane'}), 
  18.   {name'Jane'
  19. ); 

5. 处理 undefined 和 null

下面分享一下我们自己的代码中处理undefined 和null的最常见方法。

5.1 undefined 或 null 都不能作为实际值使用

举例,我们可能希望一个属性file.title永远存在,并且永远是一个字符串。有两种常见的方法来实现这一点。

这里,只检查undefined和null,而不检查一个值是否是字符串。你必须自己决定是否要把它作为一个额外的安全措施来实施。

5.1 undefined 和 null 都是禁止的

如下所示

  1. function createFile(title) { 
  2.   if (title === undefined || title === null) { 
  3.     throw new Error('`title` must not be nullish'); 
  4.   } 
  5.   // ··· 

为什么选择这种方法?

我们想把undefined和null当作一样的东西,因为JavaScript代码经常这样做--比如说。

  1. // 检测一个属性是否存在 
  2. if (!obj.requiredProp) { 
  3.   obj.requiredProp = 123; 
  4.  
  5. const myValue = myParameter ?? 'some default'

如果我们的代码中出现了问题,出现了undefined或null,我们希望它尽可能快地失败。

5.1.2 undefined 和 null 都会触发默认值

如下所示

  1. function createFile(title) { 
  2.   title ??= '(Untitled)'
  3.   // ··· 

我们不能在这里使用参数缺省值,因为它只由undefined 触发。相反,我们依靠nullish 合并赋值运算符??=。

为什么选择这种方法?

  • 我们想把undefined和null同等对待(见上一节)。
  • 我们希望我们的代码能够稳健地、无声地处理 undefined 和null。

5.2 undefined 或 null 都是“关闭”值

例如,我们可能希望一个属性file.title是一个字符串或 "关闭"(文件没有标题)。有几种方法可以实现这一点。

5.2.1 null是 "关闭的 "值

  1. function createFile(title) { 
  2.   if (title === undefined) { 
  3.     throw new Error('`title` must not be undefined'); 
  4.   } 
  5.   return {title}; 

另外,undefined可以触发一个默认值。

  1. function createFile(title = '(Untitled)') { 
  2.   return {title}; 

为什么选择这种方法?

  • 我们需要一个表示“关闭”的非值。
  • 我们不希望我们的非值触发参数默认值和解构默认值。
  • 我们想把非值字符串化为JSON(这是我们不能用undefined做的)。

5.2.2 undefined 是"关闭的 "值。

  1. function createFile(title) { 
  2.   if (title === null) { 
  3.     throw new Error('`title` must not be null'); 
  4.   } 
  5.   return {title}; 

为什么选择这种方法?

  • 我们需要一个表示“关闭”的非值。
  • 我们确实想让我们的非值触发参数默认值和解构默认值。

5.2.3 为什么不同时使用undefined和null作为 "关闭 "的值?

当接收一个值时,把undefined和null都当作 "不是一个值 "是有意义的。然而,当我们创建值时,我们希望不含糊,以便处理这些值时保持简单。

这指向了一种不同的方法。如果我们需要一个 "关闭"的值,但又不想使用undefined或null作为这样的值,怎么办?请往下看。

5.3 处理“关闭”的其他方式

5.3.1 特殊值

我们可以创建一个特殊的值,每当属性.title被关闭时,我们就使用这个值。

  1. const UNTITLED = Symbol('UNTITLED'); 
  2. const file = { 
  3.   title: UNTITLED, 
  4. }; 

5.3.2 空对象模式

空对象模式来自于面向对象的编程。

  • 一个共同的超类的所有子类都有相同的接口。
  • 每个子类都实现了一个实例运行的不同模式。
  • 其中一个模式是 "null"。

在下面的例子中,UntitledFile实现了 "null" 模式。

  1. // 抽象的超类 
  2. class File { 
  3.   constructor(content) { 
  4.     if (new.target === File) { 
  5.       throw new Error('Can’t instantiate this class'); 
  6.     } 
  7.     this.content = content; 
  8.   } 
  9.  
  10. class TitledFile extends File { 
  11.   constructor(content, title) { 
  12.     super(content); 
  13.     this.title = title; 
  14.   } 
  15.   getTitle() { 
  16.     return this.title; 
  17.   } 
  18.  
  19. class UntitledFile extends File { 
  20.   constructor(content) { 
  21.     super(content); 
  22.   } 
  23.   getTitle() { 
  24.     return '(Untitled)'
  25.   } 
  26.  
  27. const files = [ 
  28.   new TitledFile('Dear diary!''My Diary'), 
  29.   new UntitledFile('Reminder: pick a title!'), 
  30. ]; 
  31.  
  32. assert.deepEqual( 
  33.   files.map(f => f.getTitle()), 
  34.   [ 
  35.     'My Diary'
  36.     '(Untitled)'
  37.   ]); 

我们也可以只对标题使用空对象模式(而不是对整个文件对象)。

5.3.3 Maybe 类型

Maybe 类型是一种函数编程技术。

  1. function getTitle(file) { 
  2.   switch (file.title.kind) { 
  3.     case 'just'
  4.       return file.title.value; 
  5.     case 'nothing'
  6.       return '(Untitled)'
  7.     default
  8.       throw new Error(); 
  9.   } 
  10.  
  11. const files = [ 
  12.   { 
  13.     title: {kind: 'just', value: 'My Diary'}, 
  14.     content: 'Dear diary!'
  15.   }, 
  16.   { 
  17.     title: {kind: 'nothing'}, 
  18.     content: 'Reminder: pick a title!'
  19.   }, 
  20. ]; 
  21.  
  22. assert.deepEqual( 
  23.   files.map(f => getTitle(f)), 
  24.   [ 
  25.     'My Diary'
  26.     '(Untitled)'
  27.   ]); 

我们可以通过Arrays对 "just "和 "nothing "进行编码。我们的方法的好处是,它得到了TypeScript的良好支持(通过判别性联合)。

6.我的方法

我不喜欢用undefined作为 "关闭 "的值,有三个原因。

  • undefined经常意外地出现在JavaScript中。
  • ndefined会触发参数和结构化的默认值(出于同样的原因,有些人喜欢undefined)。

因此,如果我需要一个特殊的值,我会使用以下两种方法中的一种。

  • 我使用null作为一个 "关闭 "的值。(作为一个旁观者,这种方法在TypeScript中得到了比较好的支持)。)
  • 我通过上述的技术之一来避免undefined和null。这样做的好处是比较干净,坏处是涉及更多的工作。

作者:Michael Thiessen

译者:前端小智

来源:dev原文:https://2ality.com/2021/01/undefined-null-revisited.html

 

责任编辑:姜华 来源: 大迁世界
相关推荐

2021-02-04 14:24:25

5G网络运营商

2018-04-03 14:03:28

2019-10-31 15:45:25

Java薪酬语言

2020-07-31 16:54:52

戴尔

2022-05-05 09:14:41

AlpineDocker镜像开发

2018-08-27 09:28:02

Windows 95WindowsAPP

2017-01-20 16:01:26

物联网网络安全架构

2017-12-26 00:31:04

2023-07-07 08:15:18

JavaPython编写

2012-06-12 10:07:22

Web

2022-03-16 18:38:06

AI特斯拉神经网络

2020-11-24 06:00:55

PythonPython之父编程语言

2012-07-03 10:16:57

OpenFlowSDN软件定义网络

2018-01-31 11:53:13

传统企业数字化转型CIO

2015-11-09 15:19:53

2019-03-06 08:37:46

数据科学数据科学家Python

2020-11-23 10:40:08

比特币加密货币区块链

2015-11-03 14:45:58

2014-01-09 10:07:18

JavaScriptNull
点赞
收藏

51CTO技术栈公众号