Автору материала, перевод которого мы сегодня публикуем, недавно попался один вопрос на StackOverflow, который заставил его задуматься об обработке значений null
и undefined
в JavaScript. Здесь он приводит анализ текущей ситуации, показывает некоторые приёмы безопасной работы с null
и undefined
, а также, говоря о будущем, рассматривает оператор ?.
.
Основные сведения
Undefined
— это примитивное значение, которое автоматически назначается объявленным, но неинициализированным переменным. Это значение можно получить, обратившись к несуществующему свойству объекта или к несуществующему аргументу функции.
Null
— это ещё одно примитивное значение, которое представляет собой отсутствие значения. Например, если переменной присвоено значение null
, это можно трактовать как то, что на данном этапе работы программы эта переменная существует, но у неё пока нет ни типа, ни значения.
Посмотрим, что произойдёт, если выполнить такой код:
let obj;
console.log(obj.someProp);
Он выдаст следующую ошибку:
TypeError: Cannot read property 'someProp' of undefined
Похожее можно видеть и при попытках работы с переменными, имеющими значение null
.
Проверка на null и undefined
Как избежать подобных явлений? К счастью для нас, JavaScript поддерживает вычисление логических выражений по сокращённой схеме (short-circuit evaluation). Это означает, что для того, чтобы избежать вышеописанной ошибки TypeError
, можно написать следующий код:
let obj;
console.log(obj && obj.someProp); // вывод undefined
Но что если нужно пойти глубже, например, добраться до чего-то вроде obj.prop1.prop2.prop3
? В подобной ситуации можно попытаться решить задачу, выполнив множество проверок:
console.log( obj && obj.prop1 && obj.prop1.prop2 && obj.prop1.prop2.prop3 );
Хотя это и работает, смотрятся такие конструкции не очень-то хорошо.
А что если нам нужно выводить некое стандартное значение, если в подобной цепочке встречается undefined
или null
? Это возможно, но тогда кода придётся писать ещё больше:
const evaluation = obj && obj.prop1 && obj.prop1.prop2 && obj.prop1.prop2.prop3;
console.log( evaluation != null ? evaluation : "SomeDefaultValue" );
Прежде чем продолжить размышления о null
и undefined
в JavaScript, поговорим о том, как подобные значения обрабатываются в других языках.
Другие языки
Надо отметить, что проблема работы с неопределёнными значениями присутствует в большинстве языков программирования. Посмотрим, как проводятся проверки, подобные вышеописанным, в других языках.
▍Java
В Java есть api Optional
:
SomeClass object;
Optional.ofNullable(object)
.map(obj -> obj.prop1)
.map(obj -> obj.prop2)
.map(obj -> obj.prop3)
.orElse("SomeDefaultValue");
▍Kotlin
В Kotlin (он, как и Java, использует JVM) существуют операторы elvis (?:
) и safe-call (?.
).
val object: SomeClass?
object?.prop1?.prop2?.prop3 ?: "SomeDefaultValue";
▍C#
И, наконец, в C# есть операторы null-condition (?.
), и null-coalescing (??
).
SomeClass object;
object?.prop1?.prop2?.prop3 ?? "SomeDefaultValue";
Как работать с undefined в JS?
После рассмотрения возможностей по работе с неопределёнными значениями в других языках, я задался вопросом о том, есть ли возможность безопасно работать с undefined
в JavaScript и при этом не писать километры кода. Поэтому я приступил к экспериментам с регулярными выражениями. Мне хотелось создать функцию, которая позволила бы безопасно получать доступ к свойствам объектов.
function optionalAccess(obj, path, def) {
const propNames = path.replace(/]|)/, "").split(/.|[|(/);
return propNames.reduce((acc, prop) => acc[prop] || def, obj);
}
const obj = {
items: [{ hello: "Hello" }]
};
console.log(optionalAccess(obj, "items[0].hello", "def")); // Вывод Hello
console.log(optionalAccess(obj, "items[0].he", "def")); // Вывод def
После того, как я создал эту функцию, я узнал о методе lodash._get
, который имеет такую же сигнатуру:
_.get(object, path, [defaultValue])
Однако, честно говоря, я не отношусь к фанатам строковых путей, поэтому я начал искать способ, который позволил бы избежать их использования. В результате я создал решение, основанное на прокси-объектах:
// Именно здесь происходит всё самое интересное
function optional(obj, evalFunc, def) {
// Обработчик прокси
const handler = {
// Перехват всех операций доступа к свойствам
get: function(target, prop, receiver) {
const res = Reflect.get(...arguments);
//Если наш ответ является объектом, обернём его в прокси, иначе - просто вернём
return typeof res === "object" ? proxify(res) : res != null ? res : def;
}
};
const proxify = target => {
return new Proxy(target, handler);
};
// Вызовем функцию с проксированным объектом
return evalFunc(proxify(obj, handler));
}
const obj = {
items: [{ hello: "Hello" }]
};
console.log(optional(obj, target => target.items[0].hello, "def")); // => Hello
console.log(optional(obj, target => target.items[0].hell, { a: 1 })); // => { a: 1 }
Будущее безопасной работы с null и undefined в JavaScript
Сейчас в комитете TC39 имеется предложение, которое позволяет пользоваться следующими конструкциями:
obj?.arrayProp?[0]?.someProp?.someFunc?.();
По мне, так выглядит это очень аккуратно. Однако это предложение пока находится на достаточно ранней стадии согласования, то есть, его потенциальное появление в языке может занять некоторое время. Но, несмотря на это, существует плагин для babel, который позволяет уже сегодня пользоваться подобными конструкциями.
Итоги
Значения вроде null
и undefined
присутствуют в программировании уже очень давно, и ничто не указывает на то, что они в скором времени исчезнут. Вероятно, концепция значения null
является самой нелюбимой в программировании, однако у нас есть средства для обеспечения безопасной работы с подобными значениями. В том, что касается работы с null
и undefined
в JavaScript, можно воспользоваться несколькими подходами рассмотренными выше. В частности, вот код функций, предложенных автором этого материала. Если вас заинтересовал оператор ?.
, появление которого ожидается в JS, взгляните на одну из наших предыдущих публикаций, в которой затрагивается этот вопрос.
Уважаемые читатели! Какой из упомянутых в этом материале способов безопасной работы с null и undefined в JavaScript нравится вам больше всего?
Автор: ru_vds