JavaScript Nullish Coalescing (空值合併運算子)

Nullish Coalescing(空值合併運算子)是 ES2020 引入的語法,使用 ?? 運算子。它用來在變數值為 nullundefined 時提供預設值。

基本語法

var result = value ?? defaultValue;

valuenullundefined 時,回傳 defaultValue;否則回傳 value

var a = null ?? 'default';
console.log(a);  // 'default'

var b = undefined ?? 'default';
console.log(b);  // 'default'

var c = 'hello' ?? 'default';
console.log(c);  // 'hello'

?? 與 || 的差異

傳統上我們會用 || 來提供預設值:

var name = userName || 'Guest';

|| 會在左邊的值是任何 falsy 值時使用右邊的預設值。JavaScript 的 falsy 值包括:false0''(空字串)、nullundefinedNaN

這可能導致非預期的結果:

// 使用 ||
var count = 0 || 10;      // 10(0 是 falsy)
var text = '' || 'default'; // 'default'(空字串是 falsy)
var flag = false || true;   // true(false 是 falsy)

// 使用 ??
var count = 0 ?? 10;        // 0(0 不是 null/undefined)
var text = '' ?? 'default'; // ''(空字串不是 null/undefined)
var flag = false ?? true;   // false(false 不是 null/undefined)
?? 只在值是 nullundefined 時才會使用預設值,其他 falsy 值(如 0''false)會被保留。

實用範例

設定物件屬性預設值

function createUser(options) {
    return {
        name: options.name ?? 'Anonymous',
        age: options.age ?? 0,
        isAdmin: options.isAdmin ?? false,
        bio: options.bio ?? ''
    };
}

var user = createUser({ name: 'Mike' });
// { name: 'Mike', age: 0, isAdmin: false, bio: '' }

// 如果用 ||,age、isAdmin、bio 都會被覆蓋
function createUserWrong(options) {
    return {
        name: options.name || 'Anonymous',
        age: options.age || 18,        // 如果傳入 0,會變成 18
        isAdmin: options.isAdmin || false,
        bio: options.bio || 'No bio'   // 如果傳入 '',會變成 'No bio'
    };
}

處理函式參數

function greet(name, greeting) {
    name = name ?? 'Guest';
    greeting = greeting ?? 'Hello';
    
    console.log(greeting + ', ' + name + '!');
}

greet();                    // 'Hello, Guest!'
greet('Mike');              // 'Hello, Mike!'
greet('Mike', 'Hi');        // 'Hi, Mike!'
greet('Mike', '');          // ', Mike!'(空字串被保留)

存取可能不存在的資料

var user = {
    settings: {
        theme: null,
        fontSize: 0,
        showNotifications: false
    }
};

// 使用 ?? 保留有效的 falsy 值
var theme = user.settings.theme ?? 'light';          // 'light'
var fontSize = user.settings.fontSize ?? 16;         // 0(保留)
var showNotif = user.settings.showNotifications ?? true; // false(保留)

搭配 Optional Chaining

?? 經常和 Optional Chaining (?.) 一起使用:

var user = {
    profile: {
        name: 'Mike'
    }
};

// 安全存取並提供預設值
var city = user?.profile?.address?.city ?? 'Unknown';
console.log(city);  // 'Unknown'

var name = user?.profile?.name ?? 'Guest';
console.log(name);  // 'Mike'

短路求值

?? 運算子也有短路求值的特性。如果左邊不是 nullundefined,右邊的運算式不會被執行:

var count = 0;

var value = 'hello' ?? count++;  // count++ 不會執行
console.log(count);  // 0

var value2 = null ?? count++;    // count++ 會執行
console.log(count);  // 1

不能直接與 && 或 || 混用

?? 不能直接和 &&|| 混合使用,必須用括號明確指定優先順序:

// 錯誤:會拋出 SyntaxError
var result = a || b ?? c;
var result = a ?? b && c;

// 正確:使用括號
var result = (a || b) ?? c;
var result = a ?? (b && c);

這個限制是為了避免混淆,因為這三個運算子的行為不同。

Nullish Coalescing Assignment (??=)

ES2021 引入了 ??= 運算子,可以更簡潔地賦值:

var obj = { a: null, b: 'hello' };

// 只有當 a 是 null 或 undefined 時才賦值
obj.a ??= 'default';
console.log(obj.a);  // 'default'

// b 不是 null/undefined,不會被改變
obj.b ??= 'new value';
console.log(obj.b);  // 'hello'

// 等同於
obj.a = obj.a ?? 'default';

實際應用場景

設定 API 參數

async function fetchUsers(options) {
    var page = options?.page ?? 1;
    var limit = options?.limit ?? 10;
    var sortBy = options?.sortBy ?? 'createdAt';
    
    var url = '/api/users?page=' + page + '&limit=' + limit + '&sort=' + sortBy;
    return fetch(url);
}

// 使用預設值
fetchUsers({});                    // page=1, limit=10
fetchUsers({ page: 2 });           // page=2, limit=10
fetchUsers({ limit: 0 });          // limit=0(0 被保留)

表單處理

function processForm(formData) {
    return {
        email: formData.email ?? '',
        age: formData.age ?? null,
        subscribed: formData.subscribed ?? false,
        // 空字串和 0 會被保留,只有 null/undefined 才使用預設值
    };
}

瀏覽器支援

Nullish Coalescing 是 ES2020 的功能,現代瀏覽器都已支援:

瀏覽器支援版本
Chrome80+
Firefox72+
Safari13.1+
Edge80+
Node.js14+

總結

運算子使用預設值的條件
||左邊是 falsy 值(false0''nullundefinedNaN
??左邊是 nullundefined

選擇建議:

  • 如果 0''false 是有效值,應使用 ??
  • 如果想把所有 falsy 值都視為「沒有值」,使用 ||