И кажется, что будет всегда.

Как вы знаете, все значения в JavaScript являются либо примитивами, либо объектами.

Существует 7 примитивных типов данных:

-нить

-число

-большой

-логическое значение

-неопределенный

-символ

-нулевой

Проверим их типы с помощью оператора typeof:

console.log(typeof "some text");   // string
console.log(typeof 1234);    // number
console.log(typeof 9007199254740991n);  // bigint
console.log(typeof true);   // boolean
console.log(typeof undefined);    // undefined
console.log(typeof Symbol('some text'));  // symbol
console.log(typeof null);    // object

Ждать! Что?

Почему typeof null является объектом?

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

Давайте погрузимся глубоко.

В чем разница между объектами и примитивами:

Примитивы неизменяемы, к ним нельзя добавлять свойства:

let myString = "abc";
// try to add property "figure" 
myString.figure = 4; 
console.log(myString.figure) // undefined 

А примитивы сравниваются по значению, они считаются равными, если имеют одинаковое содержимое:

console.log("abc" === "abc") // true

Объекты. Все не-примитивные значения являются объектами. Объекты изменчивы:

let myObject = {};
// try to add property "figure"
myObject.figure = 123;  

console.log(myObject.figure);  // 123

А объекты сравниваются по ссылке. Каждый объект имеет свою собственную идентичность, и два объекта считаются равными только в том случае, если они на самом деле являются одним и тем же объектом:

console.log({} === {})    // false

let myObject = {};
console.log(myObject === myObject)    // true

Типы объектов-оболочек. Типы-примитивы boolean, number и string имеют соответствующие типы объектов-оболочек Boolean, Number и String. Экземпляры последних являются объектами и отличаются от примитивов, которые они обертывают:

console.log(typeof new String("abc") )    // object
console.log(typeof abc)    // string

console.log(new String("abc") === "abc")  // false
  

typeof — это оператор, который классифицирует примитивы и помогает отличать их от объектов

Проблема «typeof null» унаследована от оригинальной версии JavaScript.

В этой версии значения хранились в 32-битных блоках, которые включали тег короткого типа (1–3 бита) и фактические данные значения. Теги типа были сохранены в младших битах единиц.

теги типа (1–3 бита) + значение (29–31 бит) = всего 32 бита

Тегов было пять типов:

  • 000: объект. Данные являются ссылкой на объект.
  • 1: внутр. Данные представляют собой 31-битное целое число со знаком.
  • 010: двойной. Данные являются ссылкой на двойное число с плавающей запятой.
  • 100: строка. Данные являются ссылкой на строку.
  • 110: логическое значение. Данные являются логическими.

Есть 2 специальных значения:

  • undefined (JSVAL_VOID) — целое число −2³⁰ (число вне диапазона целых чисел).
  • null (JSVAL_NULL) был указателем NULL в машинном коде. Или: тег типа объекта плюс нулевая ссылка.

Поскольку он начинается с 000 (это тег типа объекта), тип считает, что это объект.

Код движка для 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;
    }

Он начинается с проверки того, является ли значение v неопределенным (VOID) в операторе:

если (JSVAL_IS_VOID(v))

Затем он проверяет, является ли значение объектом. Проверка наличия тега типа, начинающегося с 000.

Если это не объект, он проверяет, является ли это числом, строкой или логическим значением.

Нет никакой проверки на NULL.

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

Даже создатель JavaScript Брендан Эйх прокомментировал в блоге эту тему, заявив, что это действительно ошибка.

Брендан Эйх однажды подумал о необходимости исправления typeof, но отклонил это предложение, заявив: «Я думаю, что уже слишком поздно исправлять typeof. Изменение, предложенное для typeof null, нарушит существующий код».

В другом обсуждении он написал: «В общем, typeof кажется беспорядком, который будет трудно разумно реформировать. У нас есть основания полагать, что typeof null === «object» *является ошибкой*, которая может укусить реальный контент из нашего поиска в Интернете. Возможно, было бы лучше оставить typeof в покое и объявить его устаревшим, *но я по-прежнему поддерживаю нулевое исправление ошибок*».

Больше контента на plainenglish.io. Подпишитесь на нашу бесплатную еженедельную рассылку новостей. Получите эксклюзивный доступ к возможностям написания и советам в нашем сообществе Discord.