Очень странные скрипты

Очень странные скрипты
Изображение из блога auth0
👋
Хочешь поучаствовать в жизни сайта? Мы ищем авторов!

Это история о странном и загадочном. История об измерениях, монстрах и JavaScript.

Перевод статьи Juan Cruz Martinez: Stranger Scripts

Оглавление

1995 год, небольшой американский городок. Компания друзей играет в Dungeons & Dragons. Когда Демогоргон включается в игру, Брендан кастует фаерболл. Он бросает кубик, но кубик падает на пол, и ребята не могут его найти. Они продолжают играть дальше.

Когда игра закончилась, Брендан едет домой на своем байке через лес мимо корпорации Netscape Communications. Вдруг он видит нечто странное впереди. Он теряет равновесие, скатывается с крутого откоса и падает.

Мальчик бежит домой. С дрожью он оглядывается назад, в страхе, что та штука преследует его. Он прибегает домой, но там никого нет. Он пытается позвонить родителям, но не может дозвониться.

Что-то открывает дверь. Брендан выбегает во двор, увидев это. Загорается яркий свет, пропадает, и там больше никого нет.

Спустя 10 дней поисков Брендана Эйха находят в лесу, но он не один. С ним желтое и загадочное существо со странными характеристиками, но властным присутствием.


Чудак с Кленовой улицы

Джоуи, Эндриа и Хуан гуляли по лесу, когда они увидели JavaScript. Они застыли на месте в то время, как язык программирования пытался что-то им сказать, но они не могли понять его.

С помощью интерпретатора они сумели записать странный язык, но что он вообще означает?

Перед ними лежала запись:

([![]] + [][[]])[+!+[] + [+[]]] + 
([![]] + [][[]])[+!+[] + [+!+[]+!+[]]]
+ " " +
(![] + [])[+[]] +
(![] + [])[+!+[]] +
([![]] + [][[]])[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]] + 
(![] + [])[+!+[]+!+[]+!+[]]

После некоторых раздумий, команда взялась за работу. Они разбили ее на мелкие части и разобрали по полочкам.

Во-первых, взглянем на отрывок ([![]]+[][[]]), который повторяется не раз.

Разобьем его еще мельче и получим [![]], а мы знаем, что ![] это false, что дает [false].

Затем, отрывок [][[]] сначала выглядит непонятно, но, приглядевшись, мы увидим, что он следует шаблону массив[индекс], где массив = [] и индекс = [], а обращение к пустому массиву по любому индексу возвращает undefined, даже если этот индекс - тоже массив.

Собрав все части воедино, мы получим:

([![]] + [][[]]) === [false] + undefined &&
[false] + undefined === 'falseundefined'

Итак, ребята подставили найденное:

'falseundefined'[+!+[] + [+[]]] + 
'falseundefined'[+!+[] + [+!+[]+!+[]]]
+ " " +
(![] + [])[+[]] +
(![] + [])[+!+[]] +
'falseundefined'[+!+[] + [+[]]] +
(![] + [])[!+[] + !+[]] + 
(![] + [])[+!+[]+!+[]+!+[]]

А затем:

(![] + []) === 'false'

Отсюда:

'falseundefined'[+!+[] + [+[]]] + 
'falseundefined'[+!+[] + [+!+[]+!+[]]]
+ " " +
'false'[+[]] +
'false'[+!+[]] +
'falseundefined'[+!+[] + [+[]]] +
'false'[!+[] + !+[]] + 
'false'[+!+[]+!+[]+!+[]]

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

Строки у нас есть, но нужно определиться с индексами; к счастью, есть несколько трюков на этот счет.

Например, благодаря математике JavaScript мы знаем, что +true === 1, а +false === 0. Почему? Знак + говорит JavaScript скастовать булево значение в число.

Как только мы получим эти числа, мы сможем построить любую комбинацию из них. Например, 2 может быть записано как +true+true, но что насчет двузначных чисел? Для них понадобятся такие длинные цепочки... Можем попробовать другой подход.

number1+[number2]==="number1number2", так получается, потому что суммирование тут работает как конкатенация и преобразует обе стороны операции в строки. Это поведение заложено в JavaScript, так как язык не может сложить число и массив.

И наконец, мы можем использовать эту маленькую табличку для преобразования массивов в булевы значения, чтобы получить числа:

+[] -> +false -> 0
!+[] -> !false -> true
+!+[] -> +true -> 1

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

'falseundefined'['10'] + 
'falseundefined'['12']
+ " " +
'false'[0] +
'false'[1] +
'falseundefined'['10'] +
'false'[2] + 
'false'[3]

Посчитаем символы:

i
e
" "
f
a
i
l
s

Вот оно что! Он пытался сказать "ie fails" ("ie провалился")!

Здорово, Джолли и Труп

Карла, мать Брендана, уверена, что ее сын пытается коммуницировать с ней с помощью освещения в ее доме. Карта собирается проверить это, проведя простой математический тест.

"Брендан, ты меня слышишь?" - спрашивает она. "Если ты меня слышишь, скажи, сколько будет 1 + 1", - и лампочки в доме включаются и выключаются два раза.

Во втором тесте, 3 + 1, лампочки включаются и выключаются 4 раза.

Совпадение? Она, изнемогая от страха, так не думает.

"3" - 1, и лампочки моргают дважды.

Это точно он! Еще один тест, чтобы знать наверняка.

"3"+1, но в этом раз со светом происходит что-то другое. Он включается и выключается долгое время. Когда моргание заканчивается, Карла насчитала "31".

Видимо, я не в себе. Видимо, я сошла с ума. Что это за математика?

Она не знает JavaScript, иначе она бы поняла. Лампочки были правы, согласно JavaScript, конечно же.

Если с тобой случилось то же, помни, в JavaScript:

Number + Number -> addition
Boolean + Number -> addition
Boolean + Boolean -> addition
Number + String -> concatenation
String + Boolean -> concatenation
String + String -> concatenation

Например:

3 - 1 // -> 2
 3 + 1 // -> 4
'3' - 1 // -> 2
'3' + 1 // -> '31'

'' + '' // -> ''
[] + [] // -> ''
{} + [] // -> 0
[] + {} // -> '[object Object]'
{} + {} // -> '[object Object][object Object]'

'222' - -'111' // -> 333

[4] * [4] // -> 16
[] * [] // -> 0
[4, 4] * [4, 4] // NaN

Блоха и акробат

Тони, местный шериф, который начинает верить Карле, решает проникнуть в Netscape Communications, чтобы узнать больше о JavaScript.

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

В этом измерении действуют особые правила. Например:

Math.min() > Math.max(); // -> true
Math.min() < Math.max(); // -> false

Интуитивно, это кажется странным, но в измерении JavaScript в этом есть смысл, так как:

Math.min(); // -> Infinity
Math.max(); // -> -Infinity
Infinity > -Infinity; // -> true

Но почему Math.min()===Infinity? Math.min принимает аргументы, пытается сконвертировать каждый в число и возвращает наименьший среди них. Если ты не передаешь ни одно число, ты получает бесконечность, Infinity.

То же самое, но наоборот, происходит с Math.max.

Тем временем, Тони кое с кем повстречался.

Монстр и Изнанка

Глубоко в лаборатории, где каждая комната покрыта тьмой, Тони чувствует, что рядом что-то есть. Он вызывает подмогу. Грег и Дэн спешат к нему.

Хуан, Эндреа и Джоуи знают, что единственный способ захватить JavaScript - это войти в его измерение, поэтому они включают терминал и открывают портал. Все трое запрыгивают внутрь.

В лаборатории открывается другой портал. Тони вместе с Грегом и Дэном заходят внутрь, и портал закрывается прямо за ними.

Из темноты появляется странная надпись. Она содержит вызов: "реши меня, и я тебя отпущу!"

['1', '7', '11'].map(parseInt);

Интуитивно, компания собирается ввести свою первую догадку [1, 7, 11], но Хуан, Эндреа и Джоуи останавливают их.

В этом месте не все выглядит так, как оно есть на самом деле, поэтому нам нужно обращаться с аргументами очень аккуратно, говорит Хуан.

Карла, которая нашла свою дорогу в Изнанку, прояснила задачу.

Функция parseInt принимает два аргумента: строку и основание системы счисления. Второе может быть целым числом от 2 до 36.

"То есть функция map может передавать больше одного аргумента в parseInt, что-то еще кроме строки", - добавил Джоуи.

Разобрав задачу, команда пришла к такому выражению:

['1', '7', '11'].map((currentValue, index, array) => parseInt(currentValue, index, array));

index из map передается как основание в parseInt, и поэтому результат получается не такой, как мы ожидаем.

Команда вычисляет каждое значение:

parseInt('1', 0) // 1
parseInt('7', 1) // NaN
parseInt('11', 2) // 3

Итоговый результат: [1,NaN,3].

Как только они вводят ответ, измерение начинает трястись, и открывается новый портал.

Они выбегают наружу. "Мы в безопасности. Мы дома", радуются они.

"Но эта штука все еще там. Нужно с ней разобраться", - говорит Тони.

Команда готовится к битве...

Продолжение следует.

Материал подготовлен с ❤️ редакцией Кухни IT.

Олег Ямников

Олег Ямников

Главный кухонный корреспондент.