Оператор нулевого слияния ??
(Nullish Coalescing) возвращает правый операнд, если левый равен null
или undefined
, в отличие от оператора ||
, который проверяет на “ложность”:
// Оператор ??
const name = null ?? "Гость"; // "Гость"
const age = 0 ?? 18; // 0 (не заменяется!)
const count = "" ?? "пусто"; // "" (не заменяется!)
// Сравнение с ||
const name2 = null || "Гость"; // "Гость"
const age2 = 0 || 18; // 18 (заменяется!)
const count2 = "" || "пусто"; // "пусто" (заменяется!)
Рекомендация: Используйте ??
для работы с null
/undefined
, а ||
для работы с “ложными” значениями.
// Базовый синтаксис
const result = leftValue ?? rightValue;
// Примеры
const userName = user.name ?? "Анонимный пользователь";
const userAge = user.age ?? 0;
const userSettings = user.settings ?? {};
Оператор ??
возвращает правый операнд только если левый равен:
null
undefined
// Срабатывает (возвращает правый операнд)
console.log(null ?? "default"); // "default"
console.log(undefined ?? "default"); // "default"
// НЕ срабатывает (возвращает левый операнд)
console.log(0 ?? "default"); // 0
console.log("" ?? "default"); // ""
console.log(false ?? "default"); // false
console.log(NaN ?? "default"); // NaN
Значение | ?? “default” | || “default” | Объяснение |
---|---|---|---|
null | "default" | "default" | Оба оператора заменяют |
undefined | "default" | "default" | Оба оператора заменяют |
0 | 0 | "default" | ?? сохраняет, || заменяет |
"" | "" | "default" | ?? сохраняет, || заменяет |
false | false | "default" | ?? сохраняет, || заменяет |
NaN | NaN | "default" | ?? сохраняет, || заменяет |
"text" | "text" | "text" | Оба сохраняют |
42 | 42 | 42 | Оба сохраняют |
// Работа с числами
const score = 0;
console.log(score ?? 100); // 0 (сохраняет ноль)
console.log(score || 100); // 100 (заменяет ноль)
// Работа со строками
const message = "";
console.log(message ?? "Нет сообщения"); // "" (сохраняет пустую строку)
console.log(message || "Нет сообщения"); // "Нет сообщения" (заменяет)
// Работа с boolean
const isVisible = false;
console.log(isVisible ?? true); // false (сохраняет false)
console.log(isVisible || true); // true (заменяет false)
// ❌ Проблема с ||
function getUserSettings(user) {
return {
theme: user.theme || "light", // Проблема: "" станет "light"
fontSize: user.fontSize || 16, // Проблема: 0 станет 16
notifications: user.notifications || true // Проблема: false станет true
};
}
const user = {
theme: "", // Пользователь хочет пустую тему
fontSize: 0, // Пользователь хочет минимальный размер
notifications: false // Пользователь отключил уведомления
};
console.log(getUserSettings(user));
// { theme: "light", fontSize: 16, notifications: true } - НЕ то, что хотел пользователь!
// ✅ Правильно с ??
function getUserSettingsCorrect(user) {
return {
theme: user.theme ?? "light",
fontSize: user.fontSize ?? 16,
notifications: user.notifications ?? true
};
}
console.log(getUserSettingsCorrect(user));
// { theme: "", fontSize: 0, notifications: false } - Именно то, что хотел пользователь!
// Обработка ответа от API
function processApiResponse(response) {
// ❌ Плохо с ||
const badResult = {
id: response.id || "unknown",
count: response.count || 0, // Проблема: если count = 0
isActive: response.isActive || false // Проблема: если isActive = false
};
// ✅ Хорошо с ??
const goodResult = {
id: response.id ?? "unknown",
count: response.count ?? 0,
isActive: response.isActive ?? false
};
return goodResult;
}
// Тестовые данные
const apiResponse = {
id: "user123",
count: 0, // Валидное значение!
isActive: false // Валидное значение!
};
console.log(processApiResponse(apiResponse));
// { id: "user123", count: 0, isActive: false }
// Загрузка конфигурации
function loadConfig(userConfig = {}) {
const defaultConfig = {
apiUrl: "https://api.example.com",
timeout: 5000,
retries: 3,
debug: false
};
return {
apiUrl: userConfig.apiUrl ?? defaultConfig.apiUrl,
timeout: userConfig.timeout ?? defaultConfig.timeout,
retries: userConfig.retries ?? defaultConfig.retries,
debug: userConfig.debug ?? defaultConfig.debug
};
}
// Пользователь хочет отключить отладку и установить 0 повторов
const userConfig = {
timeout: 0, // Без таймаута
retries: 0, // Без повторов
debug: false // Без отладки
};
console.log(loadConfig(userConfig));
// Сохранит пользовательские настройки: timeout: 0, retries: 0, debug: false
// Проверка нескольких источников
const userName =
user.preferredName ??
user.firstName ??
user.email ??
"Гость";
// Эквивалентно:
let userName2;
if (user.preferredName !== null && user.preferredName !== undefined) {
userName2 = user.preferredName;
} else if (user.firstName !== null && user.firstName !== undefined) {
userName2 = user.firstName;
} else if (user.email !== null && user.email !== undefined) {
userName2 = user.email;
} else {
userName2 = "Гость";
}
// Безопасное обращение к вложенным свойствам
const city = user?.address?.city ?? "Не указан";
const phone = user?.contacts?.phone ?? "Не указан";
const avatar = user?.profile?.avatar?.url ?? "/default-avatar.png";
// Работа с массивами
const firstItem = data?.items?.[0] ?? "Нет элементов";
const lastItem = data?.items?.at?.(-1) ?? "Нет элементов";
// Оператор ??= (ES2021)
let config = {};
// Устанавливает значение только если оно null или undefined
config.theme ??= "dark";
config.language ??= "ru";
config.timeout ??= 5000;
console.log(config);
// { theme: "dark", language: "ru", timeout: 5000 }
// Не перезаписывает существующие значения
config.theme = "light";
config.theme ??= "dark"; // Не изменится
console.log(config.theme); // "light"
// Инициализация кэша
class DataCache {
constructor() {
this.cache = {};
}
get(key) {
// Создаем запись в кэше только если её нет
this.cache[key] ??= this.fetchData(key);
return this.cache[key];
}
fetchData(key) {
console.log(`Загружаем данные для ${key}`);
return `Данные для ${key}`;
}
}
const cache = new DataCache();
console.log(cache.get("user1")); // Загружаем данные для user1
console.log(cache.get("user1")); // Берем из кэша
const a = 0;
const b = "";
const c = false;
const d = null;
console.log(a ?? "default");
console.log(b ?? "default");
console.log(c ?? "default");
console.log(d ?? "default");
0
, ""
, false
, "default"
a ?? "default"
→ 0
(ноль не равен null/undefined)b ?? "default"
→ ""
(пустая строка не равна null/undefined)c ?? "default"
→ false
(false не равен null/undefined)d ?? "default"
→ "default"
(null заменяется)// ❌ Проблемная функция
function createUser(data) {
return {
name: data.name || "Пользователь",
age: data.age || 18,
isAdmin: data.isAdmin || false,
score: data.score || 0
};
}
// Тестовые данные
const userData = {
name: "", // Пользователь не указал имя
age: 0, // Новорожденный
isAdmin: false, // Обычный пользователь
score: 0 // Начальный счет
};
console.log(createUser(userData));
// Что будет не так?
Проблема: Все значения будут заменены на значения по умолчанию из-за оператора ||
.
Результат: { name: "Пользователь", age: 18, isAdmin: false, score: 0 }
Исправленная версия:
function createUser(data) {
return {
name: data.name ?? "Пользователь",
age: data.age ?? 18,
isAdmin: data.isAdmin ?? false,
score: data.score ?? 0
};
}
// Теперь результат: { name: "", age: 0, isAdmin: false, score: 0 }
const user = {
profile: {
social: {
twitter: null,
facebook: undefined,
instagram: ""
}
}
};
// Найдите первую доступную социальную сеть
const socialNetwork =
user.profile.social.twitter ??
user.profile.social.facebook ??
user.profile.social.instagram ??
"Не указано";
console.log(socialNetwork); // ?
""
(пустая строка)
twitter
равен null
→ переходим к следующемуfacebook
равен undefined
→ переходим к следующемуinstagram
равен ""
→ возвращаем ""
(пустая строка не равна null/undefined)const settings = {
theme: "dark",
language: null,
notifications: undefined
};
settings.theme ??= "light";
settings.language ??= "en";
settings.notifications ??= true;
settings.newFeature ??= "enabled";
console.log(settings);
{
theme: "dark", // Не изменилось (уже было значение)
language: "en", // Изменилось (было null)
notifications: true, // Изменилось (было undefined)
newFeature: "enabled" // Добавилось (не существовало)
}
const response = {
data: {
users: [
{ id: 1, profile: { name: "Анна" } },
{ id: 2, profile: null },
{ id: 3 }
]
}
};
// Получите имена всех пользователей с fallback
const names = response.data.users.map(user =>
user.profile?.name ?? "Имя не указано"
);
console.log(names);
["Анна", "Имя не указано", "Имя не указано"]
profile.name
существует → "Анна"
profile
равен null
→ ?.name
возвращает undefined
→ ??
возвращает "Имя не указано"
profile
не существует → ?.name
возвращает undefined
→ ??
возвращает "Имя не указано"
// ✅ Хорошо: для проверки null/undefined
const userName = user.name ?? "Гость";
const userAge = user.age ?? 0;
// ❌ Плохо: для проверки "ложных" значений
const isVisible = element.visible ?? true; // Используйте || для boolean
// ✅ Хорошо: безопасное обращение к свойствам
const city = user?.address?.city ?? "Не указан";
const avatar = user?.profile?.avatar?.url ?? "/default.png";
// ❌ Плохо: длинные проверки
const city2 = (user && user.address && user.address.city) ?? "Не указан";
// ✅ Хорошо: инициализация свойств
config.timeout ??= 5000;
config.retries ??= 3;
// ❌ Плохо: избыточные проверки
if (config.timeout === null || config.timeout === undefined) {
config.timeout = 5000;
}
// ✅ Хорошо: ясно, что проверяем только null/undefined
function processData(data) {
// Используем пустой объект только если data не передан
const safeData = data ?? {};
// Используем 0 как валидное значение для счетчика
const count = safeData.count ?? 0;
return { count };
}
// ❌ Плохо: неясно, что именно проверяем
function processDataBad(data) {
const safeData = data || {}; // Заменит и пустые объекты!
const count = safeData.count || 0; // Заменит и валидный ноль!
return { count };
}
// Простой полифил для ??
if (!window.nullishCoalescing) {
function nullishCoalescing(left, right) {
return (left !== null && left !== undefined) ? left : right;
}
// Использование
const result = nullishCoalescing(value, "default");
}
// Или используйте Babel для автоматической трансформации
// Три новых оператора присваивания
let a, b, c;
// Nullish coalescing assignment
a ??= "default"; // a = a ?? "default"
// Logical OR assignment
b ||= "default"; // b = b || "default"
// Logical AND assignment
c &&= "value"; // c = c && "value"
// Мощные комбинации
const userPreferences = {
theme: user?.settings?.theme ??
localStorage.getItem("theme") ??
"system",
language: user?.profile?.language ??
navigator.language?.split("-")?.[0] ??
"en"
};
// Условное присваивание
user.lastLogin ??= new Date();
user.preferences ??= {};
user.preferences.notifications ??= true;
// ❌ Плохо: неправильный выбор оператора
function setVolume(volume) {
// Проблема: volume = 0 будет заменен на 50
this.volume = volume || 50;
}
// ✅ Хорошо: правильный выбор
function setVolume(volume) {
// 0 - валидное значение для громкости
this.volume = volume ?? 50;
}
// ❌ Плохо: ?? не подходит для boolean логики
function isFeatureEnabled(flag) {
// Проблема: false не будет заменен на true
return flag ?? true;
}
// ✅ Хорошо: используйте || для boolean
function isFeatureEnabled(flag) {
return flag !== false; // Или другая логика
}
// ❌ Плохо: избыточно
const value = (data !== null && data !== undefined) ? data : "default";
// ✅ Хорошо: лаконично
const value = data ?? "default";
Золотые правила:
??
для проверки null
/undefined
— это точно и предсказуемо||
для проверки “ложных” значений — когда нужно заменить 0
, ""
, false
?.
) — для безопасного доступа к свойствам??=
для инициализации — лаконично и читаемоПомните: Оператор ??
решает конкретную задачу — работу с null
и undefined
. Не пытайтесь использовать его везде, выбирайте правильный инструмент для каждой ситуации.
Хотите больше статей для подготовки к собеседованиям? Подписывайтесь на EasyAdvice(@AleksandrEmolov_EasyAdvice), добавляйте сайт в закладки и прокачивайтесь каждый день 💪