928 字
5 分鐘

JavaScript 解構預設值遇到 null 為什麼失效?分清欄位與參數預設

API 回傳 { limit: null },寫了 const { limit = 20 } = data 卻仍拿到 null,這是解構預設值的正常行為。只有欄位取得 undefined 時才會採用預設值;null、0、false 和空字串都會保留。

先決定 API 契約:null 是「未提供,請用預設」,還是「刻意清空」?依這個答案決定補值規則,並保留契約允許的 falsy 值。

參數預設與欄位預設,處理不同層#

MDN 解構文件說明預設值只處理缺少或 undefined。函式的 = {} 處理的是整個參數,裡面的 limit = 20 處理的是欄位。

本文兩組範例由維護流程於 2026-11-03 在 macOS、Node.js v24.13.0 執行,驗證範圍為獨立 JavaScript 範例。將以下獨立存成 .mjs,用 Node.js 執行:

import assert from 'node:assert/strict';
function readLimit({ limit = 20 } = {}) {
return limit;
}
assert.equal(readLimit(), 20);
assert.equal(readLimit(undefined), 20);
assert.equal(readLimit({}), 20);
assert.equal(readLimit({ limit: undefined }), 20);
assert.equal(readLimit({ limit: null }), null);
assert.equal(readLimit({ limit: 0 }), 0);
assert.equal(readLimit({ limit: false }), false);
assert.equal(readLimit({ limit: '' }), '');
assert.throws(() => readLimit(null), TypeError);
console.log('參數與欄位預設值檢查通過');

最後一行之前的 TypeError 來自對整個 null 進行物件解構。巢狀解構也要逐層檢查:替 options = {} 設預設,仍不會替換 options: null。

null 可以當缺值時,用 ??;型別錯誤另外拒絕#

若產品明確規定 null 與 undefined 都使用預設,nullish coalescing 的 ??符合這個條件;|| 還會替換 0、false、空字串。換運算子之前,先列出必須保留的合法值。

例如這個分頁設定允許 limit: 0 表示不載入,範圍是 0 到 100 的整數;enabled 必須是布林值。整個輸入只允許一般資料物件,null 不視為合法回應。這是自行定義的應用契約:

import assert from 'node:assert/strict';
function readOptions(input = {}) {
if (input === null || typeof input !== 'object' || Array.isArray(input) ||
![Object.prototype, null].includes(Object.getPrototypeOf(input))) {
throw new TypeError('需要一般設定物件');
}
const limit = Object.hasOwn(input, 'limit') ? (input.limit ?? 20) : 20;
const enabled = Object.hasOwn(input, 'enabled') ? (input.enabled ?? true) : true;
if (!Number.isInteger(limit) || limit < 0 || limit > 100) {
throw new TypeError('limit 必須是 0 到 100 的整數');
}
if (typeof enabled !== 'boolean') throw new TypeError('enabled 必須是布林值');
return { limit, enabled };
}
assert.deepEqual(readOptions(), { limit: 20, enabled: true });
assert.deepEqual(readOptions({ limit: null, enabled: undefined }), { limit: 20, enabled: true });
assert.deepEqual(readOptions({ limit: 0, enabled: false }), { limit: 0, enabled: false });
assert.equal(0 || 20, 20);
assert.equal(0 ?? 20, 0);
for (const input of [null, [], 3, 'x', new Date()]) {
assert.throws(() => readOptions(input), TypeError);
}
for (const limit of ['', '20', false, NaN, Infinity, -1, 101, 1.5]) {
assert.throws(() => readOptions({ limit }), TypeError);
}
assert.throws(() => readOptions({ enabled: 'false' }), TypeError);
const empty = Object.create(null);
assert.deepEqual(readOptions(empty), { limit: 20, enabled: true });
console.log('nullish 補值、合法零值與型別驗證通過');

此函式只讀物件自身的 limit/enabled,原型上的同名欄位不列入;範例適用於一般資料,會動態改變行為的 getter 或 Proxy 屬於範例之外的輸入。若輸入來自 JSON,可在解析後立即驗證,避免錯誤值進入元件才被掩蓋。

不要用補值掩蓋 API 契約#

const { limit = 20 } = response ?? {} 能讓整個 null 使用空物件,但也把「回應無效」改成「使用預設設定」。只有 API 明確允許空回應時,才適合採用這個政策。若 null 代表停用或清空,就應保留它,或轉成應用程式定義的明確狀態。

同樣地,從環境變數讀到 'false' 仍是字串,解構、?? 都不會幫你變成布林值;可對照 process.env 布林解析。若問題發生在送出更新時,則要確認 undefined 與 null 的 JSON 契約,避免補值後反而清掉伺服器原資料。

套用到實際 API、框架 props 或表單時,先把「缺欄、undefined、null、零、false、錯誤型別」列成自己的驗收資料,再選擇預設策略。

參考資料:

MDN:Destructuring

MDN:Nullish coalescing operator

JavaScript 解構預設值遇到 null 為什麼失效?分清欄位與參數預設
https://laplusda.com/posts/javascript-destructuring-default-null/
作者
Zero
發佈於
2026-11-03
許可協議
CC BY-NC-SA 4.0