Иногда undefined это defined
Перевод статьи Benedikt Meurer: Sometimes undefined is defined. Опубликовано с разрешения автора.
В JavaScript всегда была путаница с undefined
. Я попытаюсь пролить немного света на эту проблему и объяснить, почему может быть лучше (т.е. безопаснее и/или быстрее) писать void 0
в некоторых случаях.
EcmaScript содержит специальный тип Undefined, который имеет ровно одно значение, называемое undefined.
Это актуальная семантика выполнения undefined. На уровне синтаксиса undefined
это просто произвольный идентификатор – в отличие от null
, true
и false
, которые являются ключевыми словами в JavaScript. Это означает, что когда вы пишете undefined
в программе JavaScript, вы на самом деле ссылаетесь на ранее связанное имя. По умолчанию это приведет к поиску имени undefined
в глобальном объекте, что и ожидается большинством людей.
Однако, поскольку это просто обычное имя переменной, оно может использоваться как таковое произвольными способами. Например, это вполне разумный код — с точки зрения языка JavaScript:
const isDefined = (function() {
const undefined = 1;
return x => x !== undefined;
})();console.log(isDefined(undefined)); // true
console.log(isDefined(1)); // false
Благодаря весёлым языковым конструкциям, таким как eval
или with
, это может быть даже несколько скрыто. Например, с помощью eval
:
const isDefined = (function(s) {
eval(s);
return x => x !== undefined;
})('var undefined = 1;');console.log(isDefined(undefined)); // true
console.log(isDefined(1)); // false
Или используя with
:
const isDefined = (function(o) {
with(o) return x => x !== undefined;
})({undefined: 1});console.log(isDefined(undefined)); // true
console.log(isDefined(1)); // false
То же самое относится кстати и к NaN
, которое также является просто незаписываемым, неконфигурируемым свойством глобального объекта. Это довольно запутанно, и является прекрасной причиной не использовать eval
или with
в вашем коде.
Вы можете избежать этих проблем, используя оператор void
вместо undefined
, когда хотите быть уверенны, что получили настоящее значение undefined
(и поверьте мне, вы всегда этого хотите, если пишите undefined
).
Оператор void
всегда возвращает значение undefined
. Так как выражение, переданное ему, вычисляется, но результат не используется, то для нашего трюка подойдёт любое. Я предлагаю придерживаться void 0
, так как это коротко и легко читается.
const isDefined = (function() {
const undefined = 1;
return x => x !== void 0;
})();console.log(isDefined(undefined)); // false
console.log(isDefined(1)); // true
Теперь программа делает то, что мы хотим.
Очевидно, что здесь есть и аспект производительности. V8 на самом деле ведёт себя довольно умно, и избегает поиска значения глобального свойства для undefined
во многих случаях. На самом деле, V8 всегда будет сбрасывать undefined
до фактического undefined значения (и отдавать его как константу), если у вас нет никакого eval
или with
цепочке областей видимости.
Однако другие механизмы JavaScript могут не иметь такой оптимизации. Например JavaScriptCore (движок внутри Safari) не применяет эту оптимизацию для выше приведённой функции getUndefined
— по крайней мере, не на уровне байт-кода:
Резюмируя: помните, что когда вы обращаетесь к undefined
в вашем JavaScript, в зависимости от окружающего кода не всегда там может содержаться значение undefined. Возможно, будет безопаснее (и быстрее с точки зрения производительности) использовать вместо этого void 0
.
Слушайте наш подкаст в iTunes и SoundCloud, читайте нас на Medium, контрибьютьте на GitHub, общайтесь в группе Telegram, следите в Twitter и канале Telegram, рекомендуйте в VK и Facebook.