Оператор нулевого слияния ?? (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 ?? {};Оператор ?? возвращает правый операнд только если левый равен:
nullundefined// Срабатывает (возвращает правый операнд)
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, добавляйте сайт в закладки и прокачивайтесь каждый день 💪