Иногда undefined это defined

Andrey Melikhov
devSchacht
Published in
3 min readApr 24, 2017

Перевод статьи Benedikt Meurer: Sometimes undefined is defined. Опубликовано с разрешения автора.

В JavaScript всегда была путаница с undefined. Я попытаюсь пролить немного света на эту проблему и объяснить, почему может быть лучше (т.е. безопаснее и/или быстрее) писать void 0 в некоторых случаях.

EcmaScript содержит специальный тип Undefined, который имеет ровно одно значение, называемое undefined.

tc39.github.io/ecma262/#sec-ecmascript-language-types-undefined-type

Это актуальная семантика выполнения undefined. На уровне синтаксиса undefined это просто произвольный идентификатор – в отличие от null, true и false, которые являются ключевыми словами в JavaScript. Это означает, что когда вы пишете undefined в программе JavaScript, вы на самом деле ссылаетесь на ранее связанное имя. По умолчанию это приведет к поиску имени undefined в глобальном объекте, что и ожидается большинством людей.

tc39.github.io/ecma262/#sec-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).

tc39.github.io/ecma262/#sec-void-operator

Оператор 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.

Статья на GitHub

--

--

Andrey Melikhov
devSchacht

Web-developer in big IT company Перевожу всё, до чего дотянусь. Иногда (но редко) пишу сам.