FunES#10: Странный массив

Точнее все ок, магии нет

Буквально недавно на Reddit был показан интересный пример “нетипичного” поведения JS. Через некоторое время этот “мем” ушел в твиттер и его даже запостил Axel Rauschmayer‏, что придало этому примеру большой резонанс.

У меня была очень загруженная неделя, полностью отключилось критическое мышление и я тоже увидел в этом некоторую магию. Но спустя час коллега мне подсказал что я ошибся. Магии нет. Еще раз взглянув трезвым взглядом, я понял что и правда, магии нет и все работает штатно. Так о чем это я?

О том что есть у вас массив. Есть некоторый вычисляемый индекс и в какой-то момент вместо числа приходит NaN. Может такое быть? Может. И вы “создаете в массиве” элемент с индексом NaN:

const a = [];
a[NaN] = 123;

И далее у всех вызвал восторг тот факт, что при этом длинна массива нулевая. Т.е.:

const a = [];
a[NaN] = 123;console.log(a) // [ NaN: 123 ]
a.length == 0  // true
a[NaN] === 123 // true

И показалось будто бы нашелся новый баг/фича. Но если присмотреться внимательнее, то здесь нет магии. Вообще нет. И все работает согласно правилам.

Разбираемся

Массив — это объект. И ему свойственны все атрибуты объекта в JS. По сути сам массив можно было бы описать как объект со свойством length и вспомогательными методами:

const a = { 0: 'a', 1: 'b', length: 2 };
for (let i=0; i<a.length; i++) console.log(a[i]);

Так как NaN — это не число, то оно не может быть использовано как индекс массива (при том что тип NaN это number (парадокс, да)). А следовательно идет попытка создать новое свойство объекта. В JS мы можем добавить к любому объекту любое свойство в любое время (есть и исключения, да). Так как константа NaN как тип не может быть использована как свойство объекта, то происходит преобразование, а именно, вызывается toString():

NaN.toString() === 'NaN'
// поэтому
a = [];
a[NaN] = 123;
a['NaN'] === a[NaN]

Тут нет никакой магии. Смотрим на вывод console.log и видим:

Создается ложное ощущение что NaN это индекс массива. Но если внимательно присмотреться, то индексы в массиве не пишутся, а пишутся сразу значения. А NaN выводится как “индекс”:

И выходит что мы видим что NaN это свойство объекта. Т.е. запись a[NaN] эквивалентна записи a.NaN и ничем не отличается от любой другой записи:

const a = [];
a['lol'] = 123;
a.length = 0;
a['lol'] === a.lol

Почему NaN мы легко воспринимаем как число? По нескольким причинам. Одна из них это вывод типа:

typeof NaN == 'number'

Что уже как бы немного сбивает с толку. Еще одна причина по которой NaN некоторые принимают за число это его природа, так как хранится в Number:

const a = [];
a[NaN] = 123;
a[NaN] === a['NaN']
a[NaN] === a[Number.NaN]

Но NaN — это Not a number, так что это не число.

Вердикт

Для себя выводы: не писать статьи вечером после работы, высыпаться, не публиковать посты сразу, дать им отлежаться, верить в себя и не поддаваться общей истерии, перепроверять мысли :)

Александр Майоров / Alexander Mayorov — ProWEBIT

Written by

Активист программного комитета FrontendConf. CTO and co-founder at New.HR & 𝔾𝕖𝕖𝕜🄹🄾🄱.ru