关于javascript:null-和-undefined-的区别

61次阅读

共计 6348 个字符,预计需要花费 16 分钟才能阅读完成。

作者:Michael Thiessen
译者:前端小智
起源:dev

有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq449245884/xiaozhi 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

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

相比之下,JavaScript 有两个这样的非值:undefinednull。在这篇博文中,咱们将钻研它们有什么不同,以及如何最好地应用或防止它们。

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

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?.someProp
undefined
> null?.someProp
undefined

3.null 呈现状况

一个对象的原型要么是一个对象,要么在原型链的末端是 nullObject.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 进行可选的链接时

  • 如果 valueundefinednull,则返回 undefined。也就是说,只有value.prop 会抛出一个异样,就会产生这种状况。
  • 否则,返回value.prop
function getProp(value) {
  // optional static property access
  return value?.prop;
}
assert.equal(getProp({prop: 123}), 123);
assert.equal(getProp(undefined), undefined);
assert.equal(getProp(null), undefined);

上面两个操作的成果相似。

obj?.[«expr»] // optional dynamic property access
func?.(«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

上面分享一下咱们本人的代码中解决 undefinednull 的最常见办法。

5.1 undefined 或 null 都不能作为理论值应用

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

这里,只查看 undefinednull,而不查看一个值是否是字符串。你必须本人决定是否要把它作为一个额定的安全措施来施行。

5.1 undefined 和 null 都是禁止的

如下所示

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

为什么抉择这种办法?

咱们想把 undefinednull当作一样的货色,因为 JavaScript 代码常常这样做 – 比如说。

// 检测一个属性是否存在
if (!obj.requiredProp) {obj.requiredProp = 123;}

const myValue = myParameter ?? 'some default';

如果咱们的代码中呈现了问题,呈现了 undefinednull,咱们心愿它尽可能快地失败。

5.1.2 undefined 和 null 都会触发默认值

如下所示

function createFile(title) {title ??= '(Untitled)';
  // ···
}

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

为什么抉择这种办法?

  • 咱们想把 undefinednull同等对待(见上一节)。
  • 咱们心愿咱们的代码可能持重地、无声地解决 undefinednull

5.2 undefined 或 null 都是“敞开”值

例如,咱们可能心愿一个属性 file.title 是一个字符串或 “ 敞开 ”(文件没有题目)。有几种办法能够实现这一点。

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 做的)。

5.2.2 undefined 是 ” 敞开的 “ 值。

function createFile(title) {if (title === null) {throw new Error('`title` must not be null');
  }
  return {title};
}

为什么抉择这种办法?

  • 咱们须要一个示意“敞开”的非值。
  • 咱们的确想让咱们的非值触发参数默认值和解构默认值。

5.2.3 为什么不同时应用 undefined 和 null 作为 “ 敞开 “ 的值?

当接管一个值时,把 undefinednull都当作 “ 不是一个值 “ 是有意义的。然而,当咱们创立值时,咱们心愿不含糊,以便解决这些值时放弃简略。

这指向了一种不同的办法。如果咱们须要一个 “ 敞开 ” 的值,但又不想应用 undefinednull作为这样的值,怎么办?请往下看。

5.3 解决“敞开”的其余形式

5.3.1 非凡值

咱们能够创立一个非凡的值,每当属性 .title 被敞开时,咱们就应用这个值。

const UNTITLED = Symbol('UNTITLED');
const file = {title: UNTITLED,};

5.3.2 空对象模式

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

  • 一个独特的超类的所有子类都有雷同的接口。
  • 每个子类都实现了一个实例运行的不同模式。
  • 其中一个模式是 “null”。

在上面的例子中,UntitledFile实现了 “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 中失去了比拟好的反对)。)
  • 我通过上述的技术之一来防止 undefinednull。这样做的益处是比拟洁净,害处是波及更多的工作。

~ 完,我是小智,刷碗去了,当初唯有刷碗能力体验到生存中的一点乐趣,逃 ~


代码部署后可能存在的 BUG 没法实时晓得,预先为了解决这些 BUG,花了大量的工夫进行 log 调试,这边顺便给大家举荐一个好用的 BUG 监控工具 Fundebug。

原文:https://2ality.com/2021/01/un…

交换

有幻想,有干货,微信搜寻 【大迁世界】 关注这个在凌晨还在刷碗的刷碗智。

本文 GitHub https://github.com/qq44924588… 已收录,有一线大厂面试残缺考点、材料以及我的系列文章。

正文完
 0