Исследование бездны null и undefined в JavaScript
Говоря о примитивных типах данных в JavaScript, большинство имеет в виду самые основные из них: String, Number, и Boolean. Эти примитивы достаточно предсказуемы, и работают так, как от них и ожидается. Однако, речь в данной статье пойдет об менее обыденных примитивных типах, Null и Undefined, о том, в чём они схожи, различны, и, вообще говоря, необычны.
Понимание null и undefined
В JavaScript null
— это литерал и ключевое слово языка, которое представляет
собой отсутствие какого-либо объекта. Другими словами, null
указывает
«в никуда». В свою очередь, хоть и похожий по смыслу undefined
, олицетворяет
отсутствие значения как такового. Оба абсолютно неизменны, не имеют свойств и
методов и не способны их иметь. Фактически, попытка обратиться к какому-нибудь
свойству, или присвоить его, приведёт к ошибке TypeError
. Оба этих
примитива, как намекают их имена, совершенно лишены значений.
Это самое отсутствие значения приводит к тому, что они считаются ложными, в
том смысле, что они приводятся к false
если используются в качестве условия,
например, в конструкции if
. А если сравнить null
и undefined
с другими
ложными значениями при помощи оператора нестрогого сравнения (==
), то
окажется, что они не равны ничему, кроме самих себя:
null == 0; // false
undefined == ""; // false
null == false; // false
undefined == false; // false
null == undefined; // true
Несмотря на эти сходства, null
и undefined
не эквивалентны. Каждый из
них является представителем своего типа: undefined
— представляет тип
Undefined, а null
, соответственно — тип Null. Это легко доказать,
сравнив их при помощи оператора строгого сравнения (===
), который
принимает в расчёт не только значения, но и типы данных:
undefined === null; // false
Это важное различие, и оно не случайно, ведь эти примитивы служат для разных
целей. Чтобы их различать, вы можете считать undefined
неожиданным
отсутствием значения, а null
— умышленным отсутствием значения.
Получение undefined
Есть множество способов получить значение undefined
в коде. Обычно это
происходит при попытке получить значение там, где значения нет.
В этом случае JavaScript, будучи динамическим, слабо типизированным языком,
не покажет ошибку, а выдаст значение по умолчанию, undefined
.
Любая объявленная переменная, которой при создании не присвоено никакого
значения, имеет значение undefined
:
var foo; // по умолчанию undefined
Значение undefined
также получается при попытке обратиться к несуществующему
свойству объекту или элементу массива:
var array = [1, 2, 3];
var foo = array.foo; // свойство foo не существует, возвращается undefined
var item = array[5]; // в массиве нет элемента 5, возвращается undefined
Если в функции нет оператора return
, она возвращает undefined
:
var value = (function(){})(); // возвращает undefined
Если функции не был передан какой-либо аргумент, он становится undefined
:
(function(undefined){
// параметр равен undefined
})();
Помимо всего вышеперечисленного, для получения undefined
может
использоваться оператор void
. Некоторые библиотеки, вроде Underscore
пользуются этим для надежной проверки типов, потому как void
нельзя
переопределить, и он всегда возвращает undefined
:
function isUndefined(obj){
return obj === void 0;
}
Наконец, undefined
— это предопределённая глобальная переменная (а не
ключевое слово, как null
), которая равна undefined
:
'undefined' in window; // true
Начиная с пятой версии ECMAScript эта переменная доступна только для чтения, а, вот, в предыдущих версиях её было возможно переопределить.
Применение null
В первую очередь null
отличается своим применением, и в отличие от
undefined
, null
больше используется для присваивания значения. Как раз
из-за этого оператор typeof
для null
возвращает «object». Изначально это
объяснялось тем, что null
использовался (и используется) как пустая ссылка
там, где ожидается объект, что-то вроде заглушки. Такое поведение typeof
было позже признано багом, и, хотя было предложено это поведение
исправить, пока что, в целях обратной совместимости, всё остается как есть.
Вот, почему окружение JavaScript не выставляет никаких значений в null
, и
это делается только программно. В документации на MDN написано следующее:
В различных API
null
часто возвращается в тех местах, где ожидается объект, но такой объект подобрать нельзя.
Это правдиво для DOM, который не зависит от языка и никак не описывается в
документации ECMAScript. Из-за того, что используется внешний API, попытка
получить отсутствующий элемент возвращает null
, а не undefined
.
Вообще, если нужно присвоить «не-значение» переменной или свойству,
передать его в функцию, или вернуть из функции, то null
— это почти всегда
лучший вариант. Упрощённо: JavaScript использует undefined
, а программисты
должны использовать null
.
Другой способ применения null
— явное «зануливание» переменной
(object = null
), когда ссылка на объект больше не требуется. Кстати, это
считается хорошей практикой. Присваивая null
, вы фактически удаляете ссылку
на объект, и если на него нет других ссылок, он отправляется к сборщику
мусора, таким образом возвращая доступную память.
Копнём глубже
Причина того, что null
и undefined
эдакие чёрные дыры, кроется не только в
их поведении, но ещё и в том, как они обрабатываются внутри окружения
JavaScript. Они не обладают теми характеристиками, которые обычно присущи
другим примитивам и встроенным объектам.
Начиная с ES5 метод Object.prototype.toString
, ставший стандартом де-факто
для проверки типов, стал полезен в этом отношении и для null
с undefined
:
Object.prototype.toString.call(null); // [object Null]
Object.prototype.toString.call(undefined); // [object Undefined]
Однако, на самом деле у null
и undefined
этот метод не возвращает
внутреннее свойство [[Class]]
. По документации он работает следующим
образом:
- Если значение
this
равноundefined
, вернуть"[object Undefined]"
.- Если значение
this
равноnull
, вернуть"[object Null]"
.- Пусть O равно результату вызова
ToObject
сthis
, переданным как аргумент.- Пусть class равно внутреннему свойству
[[Class]]
объекта O.- Вернуть значение String, которое является результатом сложения трёх строк
"[object "
, class, и"]"
.
Этот метод просто возвращает заготовленную строку, если обнаруживает null
или undefined
, просто чтобы унифицировать функциональность с другими
объектами. Такое поведение встречается сплошь и рядом во всей документации,
большая часть методов содержат простую проверку, и если встретился null
или
undefined
, возвращают значение сразу. Фактически, нигде не написано, что у
них содержатся какие-либо внутренние свойства, обычно имеющиеся у каждого
нативного объекта. Это как если бы они вообще не были объектами. Интересно,
эти примитивы в окружении JavaScript как-то явно и особо обрабатываются? Может
быть, кто-то более знакомый с имплементацией мог бы подсказать.
Заключение
Неважно, насколько необычными кажутся примитивы null
и
undefined
, понимание разницы между ними и их различиями в использовании согласно JavaScript очень важно для понимания языка в целом.
Это понимание, конечно же, само по себе не заставит работать ваше приложение или, например, не сломает его, но, строго говоря, оно положительно
скажется в долгосрочной перспективе, облегчив вам разработку и отладку будущих проектов.
Комментарий переводчика
При написании этой статьи автор забыл упомянуть одну важную деталь: у примитивов, как таковых, не может быть свойств вообще, они есть только у объектов. А при попытке получить свойство у примитива, он будет неявно преобразован в объект. В этом легко убедиться:
var s = 'test', o = Object(s);
o.foo = 42;
s.foo = 42;
o.foo; // 42
s.foo; // undefined
Дело в том, что null
и undefined
просто нельзя преобразовать в объект, на чем и строится объяснение ключевых особенностей этих примитивов автором этой статьи.
Также, фраза про то, что null
в окружении JavaScript без явного присваивания
не используется, неверна. В конце цепочки прототипов находится null
, и это как раз тот случай, когда ожидается объект, но его нет:
Object.getPrototypeOf(Object.prototype); // null