[Перевод] Настоящее и будущее безопасной работы с null и undefined в JavaScript

Автору материала, перевод которого мы сегодня публикуем, недавно попался один вопрос на StackOverflow, который заставил его задуматься об обработке значений null и undefined в JavaScript. Здесь он приводит анализ текущей ситуации, показывает некоторые приёмы безопасной работы с null и undefined, а также, говоря о будущем, рассматривает оператор ?..

image

Основные сведения


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 нравится вам больше всего?

© Habrahabr.ru