面向急性子的程序员的 JavaScript(ES2022 版)
请支持本书:购买捐赠
(广告,请不要屏蔽。)

14 非值 undefinednull



许多编程语言都有一个称为 null 的“非值”。它表示变量当前没有指向任何对象 - 例如,当它尚未初始化时。

相比之下,JavaScript 有两个这样的值:undefinednull

14.1 undefinednull

这两个值非常相似,并且经常可以互换使用。因此,它们之间的区别很微妙。语言本身做了如下区分

程序员可以做如下区分

14.2 undefinednull 的出现

以下小节描述了 undefinednull 在语言中出现的位置。我们将遇到几种机制,这些机制将在本书后面详细解释。

14.2.1 undefined 的出现

未初始化的变量 myVar

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

未提供参数 x

function func(x) {
  return x;
}
assert.equal(func(), undefined);

缺少属性 .unknownProp

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

如果我们没有通过 return 语句显式指定函数的结果,JavaScript 会为我们返回 undefined

function func() {}
assert.equal(func(), undefined);

14.2.2 null 的出现

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

> Object.getPrototypeOf(Object.prototype)
null

如果我们将正则表达式(例如 /a/)与字符串(例如 'x')匹配,我们将获得一个包含匹配数据的对象(如果匹配成功)或 null(如果匹配失败)

> /a/.exec('x')
null

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

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

14.3 检查 undefinednull

检查两者

if (x === null) ···
if (x === undefined) ···

x 是否有值?

if (x !== undefined && x !== null) {
  // ···
}
if (x) { // truthy?
  // x is neither: undefined, null, false, 0, NaN, ''
}

xundefined 还是 null

if (x === undefined || x === null) {
  // ···
}
if (!x) { // falsy?
  // x is: undefined, null, false, 0, NaN, ''
}

真值 表示“如果强制转换为布尔值,则为 true”。假值 表示“如果强制转换为布尔值,则为 false”。这两个概念在 §15.2 “假值和真值” 中有详细解释。

14.4 空值合并运算符 (??) 用于默认值 [ES2020]

有时我们接收一个值,并且只希望在它不是 nullundefined 时才使用它。否则,我们希望使用默认值作为备用。我们可以通过 *空值合并运算符* (??) 来做到这一点

const valueToUse = receivedValue ?? defaultValue;

以下两个表达式是等效的

a ?? b
a !== undefined && a !== null ? a : b

14.4.1 示例:统计匹配项

以下代码展示了一个真实的例子

function countMatches(regex, str) {
  const matchResult = str.match(regex); // null or Array
  return (matchResult ?? []).length;
}

assert.equal(
  countMatches(/a/g, 'ababa'), 3);
assert.equal(
  countMatches(/b/g, 'ababa'), 2);
assert.equal(
  countMatches(/x/g, 'ababa'), 0);

如果在 str 中有一个或多个 regex 的匹配项,则 .match() 返回一个数组。如果没有匹配项,它会返回 null(而不是空数组)。我们通过 ?? 运算符来解决这个问题。

我们也可以使用 可选链

return matchResult?.length ?? 0;

14.4.2 示例:为属性指定默认值

function getTitle(fileDesc) {
  return fileDesc.title ?? '(Untitled)';
}

const files = [
  {path: 'index.html', title: 'Home'},
  {path: 'tmp.html'},
];
assert.deepEqual(
  files.map(f => getTitle(f)),
  ['Home', '(Untitled)']);

14.4.3 使用解构为默认值

在某些情况下,解构也可以用于默认值 - 例如

function getTitle(fileDesc) {
  const {title = '(Untitled)'} = fileDesc;
  return title;
}

14.4.4 传统方法:使用逻辑或 (||) 为默认值

在 ECMAScript 2020 和空值合并运算符之前,逻辑或用于默认值。这有一个缺点。

|| 对于 undefinednull 来说工作正常

> undefined || 'default'
'default'
> null || 'default'
'default'

但它也会为所有其他假值返回默认值 - 例如

> false || 'default'
'default'
> 0 || 'default'
'default'
> 0n || 'default'
'default'
> '' || 'default'
'default'

将其与 ?? 的工作方式进行比较

> undefined ?? 'default'
'default'
> null ?? 'default'
'default'

> false ?? 'default'
false
> 0 ?? 'default'
0
> 0n ?? 'default'
0n
> '' ?? 'default'
''

14.4.5 空值合并赋值运算符 (??=) [ES2021]

??= 是一个 逻辑赋值运算符。以下两个表达式大致等效

a ??= b
a ?? (a = b)

这意味着 ??=短路的:只有当 aundefinednull 时才会进行赋值。

14.4.5.1 示例:使用 ??= 添加缺少的属性
const books = [
  {
    isbn: '123',
  },
  {
    title: 'ECMAScript Language Specification',
    isbn: '456',
  },
];

// Add property .title where it’s missing
for (const book of books) {
  book.title ??= '(Untitled)';
}

assert.deepEqual(
  books,
  [
    {
      isbn: '123',
      title: '(Untitled)',
    },
    {
      title: 'ECMAScript Language Specification',
      isbn: '456',
    },
  ]);

14.5 undefinednull 没有属性

undefinednull 是仅有的两个 JavaScript 值,如果我们尝试读取它们的属性,就会得到一个异常。为了探究这种现象,让我们使用以下函数,该函数读取(“获取”)属性 .foo 并返回结果。

function getFoo(x) {
  return x.foo;
}

如果我们将 getFoo() 应用于各种值,我们可以看到它只对 undefinednull 失败

> getFoo(undefined)
TypeError: Cannot read properties of undefined (reading 'foo')
> getFoo(null)
TypeError: Cannot read properties of null (reading 'foo')

> getFoo(true)
undefined
> getFoo({})
undefined

14.6 undefinednull 的历史

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

在 JavaScript 中,每个变量都可以保存对象值和基本类型值。因此,如果 null 表示“不是对象”,那么 JavaScript 还需要一个初始化值来表示“既不是对象也不是基本类型值”。该初始化值是 undefined

  测验

参见 测验应用程序