История typeof null в JavaScript – ошибка, необходимая для обратной совместимости

В JavaScript есть множество исключений и просто забавных подходов к преобразованию типов. Про "один из них" и хочется рассказать.

ПрограммыJavaScriptWebКодАнти-паттернЯзык Си

Перевод статьи "The history of “typeof null”".

В JavaScript typeof null вернёт "объект", так что мы можем предположить, что null - это объект. Однако, это не так. Это ошибка, и она, к сожалению, не может быть исправлена, потому что это бы поломало существующий код. Давайте рассмотрим историю этой ошибки.

Ошибка "typeof null" - это наследие первой версии JavaScript. В этой версии значения хранились в 32 битах, которые состояли из небольшого тега типа значения (1-3 бита) и данных значения. Типы значения хранились в младших битах. Их было пятеро:

  • 000: object. Данные - это ссылка на объект.
  • 1: int. Эти данные представляют собой 31-битное целое число со знаком.
  • 010: double. Данные – ссылка на число с плавающей точкой двойной точности.
  • 100: string. Ссылка на строку.
  • 110: boolean. Логические.

То есть самый младший бит был либо 1, тогда тип имеет длину в один бит. Или он 0, тогда тип был длиной в три бита, обеспечивая два дополнительных бита для четырех типов.

Также было два специальных значения:

  • undefined (JSVAL_VOID) был целым числом значением −2 в 30-ой степени.
  • null (JSVAL_NULL) был машинным указателем NULL (тип - объект, указатель NULL).

Теперь должно быть понятно, почему typeof null был объектом: он смотрел на биты типа, по которым null был честным "объектом". Код функции typeof:

JS_PUBLIC_API(JSType)
JS_TypeOfValue(JSContext *cx, jsval v)
{
    JSType type = JSTYPE_VOID;
    JSObject *obj;
    JSObjectOps *ops;
    JSClass *clasp;

    CHECK_REQUEST(cx);
    if (JSVAL_IS_VOID(v)) {  // (1)
        type = JSTYPE_VOID;
    } else if (JSVAL_IS_OBJECT(v)) {  // (2)
        obj = JSVAL_TO_OBJECT(v);
        if (obj &&
            (ops = obj->map->ops,
             ops == &js_ObjectOps
             ? (clasp = OBJ_GET_CLASS(cx, obj),
                clasp->call || clasp == &js_FunctionClass) // (3,4)
             : ops->call != 0)) {  // (3)
            type = JSTYPE_FUNCTION;
        } else {
            type = JSTYPE_OBJECT;
        }
    } else if (JSVAL_IS_NUMBER(v)) {
        type = JSTYPE_NUMBER;
    } else if (JSVAL_IS_STRING(v)) {
        type = JSTYPE_STRING;
    } else if (JSVAL_IS_BOOLEAN(v)) {
        type = JSTYPE_BOOLEAN;
    }
    return type;
}

По шагам:

На шаге (1) проверяем на значение undefined (VOID). На самом деле, за JSVAL_IS_VOID скрывается макрос:

#define JSVAL_IS_VOID(v) ((v) == JSVAL_VOID)

Следующая проверка (2) – имеет ли значение младшие байты, которые укажут на тип объекта. Если он в добавок является вызываемым (3), либо его свойство (поле) [[Class]] помечает его как функцию (4), то это функция. В противном случае – это объект. Вот так и получаем, что null – это объект.

Последующие проверки выполняются для number, string и boolean. Но нет проверки на null, которая могла бы быть подобной той, что есть для undefined.

Это может показаться очевиднейшей ошибкой, но не забывайте, что было очень мало времени, чтобы закончить первую версию JavaScript.

Можете сами ознакомиться с ранней версией JS.

Фото Почему Python - не язык программирования будущего
Почему Python - не язык программирования будущего

Даже если он будет пользоваться большим спросом еще несколько лет...

Фото Антипаттерн "Заочный менеджер". Ниндзя офисного мира
Антипаттерн "Заочный менеджер". Ниндзя офисного мира

Менеджер, который не мешает работать... ну не идеал ли? Другое дело, если он и не помогает - зачем он нужен?