針對急躁的程式設計師的 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, ''
}

xundefinednull 嗎?

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

  測驗

請參閱 測驗應用程式