Pull to refresh

JavaScript: проверьте свою интуицию

Reading time 4 min
Views 30K

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

Ответы и свой вариант объяснения почему такое поведение логично, как и в прошлый раз, я буду скрывать под спойлером. Сразу оговорюсь, что не претендую на непоколебимую истину своих версий и буду рад их обсудить. В разгадывании вам может помочь отличный русский перевод спецификации ECMAScript 5, за который большое спасибо iliakan!

1. Главный вопрос жизни, вселенной и всего такого

"3" -+-+-+ "1" + "1" / "3" * "6" + "2"

Решение
"3" -+-+-+ "1" + "1" / "3" * "6" + "2" == "42"

Как мы уже знаем из прошлого поста оператор “+” выполняет либо конкатенацию строк, либо сложение, либо приведение к числу. Т.е.
“3” + 2 == “32”

а не 5, как можно было бы подумать.

Кроме этого, вы можете бросить унарный оператор “+” или “-” на выражение, чтобы изменить его знак и сделать вещи красивее и читабельнее.

С учетом всего вышесказанного:
+ “1” === 1
-+ “1” === -1
+-+ “1” === -1
…
-+-+-+ === -1
"3" -+-+-+ "1" === 2  //вычитание производится над числами


Дальше вспомним приоритет операций: / * +
“1”/”3” === 0.3333…
“1” / ”3” * ”6” === 2
2 + 2 === 4
4 + “2” === “42”



2. Максимализм

Math.max(3, 0); 
Math.max(3, {});
Math.max(3, []);
Math.max(-1, [1]);
Math.max(-1, [1, 4]);
Math.max(3, true);
Math.max(3, 'foo');
Math.max(-1, null);
Math.max(-1, undefined);

Решение
Math.max(3, 0);           // 3

Тут все просто.

Math.max(3, {});          // NaN

попытаемся выполнить toNumber для {}:
выполним valueOf и проверим примитив ли это — нет, тогда
выполним toString и получим "[object Object]";
выполним для получившейся строки toNumber и получим NaN;
в случае получения NaN Math.max() всегда возвращает NaN.

Math.max(3, []);          // 3

попытаемся выполнить toNumber для []:
выполним valueOf и проверим примитив ли это — нет, тогда
выполним toString и получим "";
выполним для получившейся строки toNumber и получим 0;
3 > 0

Math.max(-1, [1]);     // 1

попытаемся выполнить toNumber для [1]:
выполним valueOf и проверим примитив ли это — нет, тогда
выполним toString и получим «1»;
выполним для получившейся строки toNumber и получим 1;
-1 < 1

Math.max(-1, [1, 4]);  // NaN

попытаемся выполнить toNumber для [1,4]:
выполним valueOf и проверим примитив ли это — нет, тогда
выполним toString и получим «1,4»;
выполним для получившейся строки toNumber и получим NaN;
в случае получения NaN Math.max() всегда возвращает NaN.

Math.max(3, true);        // 3

попытаемся выполнить toNumber для true:
выполним valueOf и проверим примитив ли это — да, тогда
toNumber(true) === 1

3 > 1

Math.max(3, 'foo');       // NaN

для 'foo' toNumber возвращает NaN.
В случае получения NaN Math.max() всегда возвращает NaN.

Math.max(-1, null);       // 0

toNumber(null) === 0
-1 < 0


Math.max(-1, undefined);  // NaN

toNumber(undefined) === NaN

в случае получения NaN Math.max() всегда возвращает NaN.


3. Жизнь запятой

[,,,].join()
[,,,undefined].join()

Скрытый текст
[,,,].join()      // ",,"
[,,,undefined].join()    // ",,,"

Тут я могу сослатся только на Флэнагана. 7.1:
«Если литерал массива содержит несколько идущих подряд запятых без значений между ними, создается разреженный массив. Элементы, соответствующие таким пропущенным значениям, отсутствуют в массиве, но при обращении к ним возвращается значение undefined.»
«Синтаксис литералов массивов позволяет вставлять необязательную завершающую запятую.»
Зачем так сделано мне не понятно. В комментариях к прошлому посту это преподносилось как фича. По моему это все таки браузерный костыль от ошибок в коде.


4. Карта сокровищ

Array(20).map(function(elem) { return 'a'; });

Скрытый текст
Array(20).map(function(elem) { return 'a'; }); // Array of undefined x 20

Вызов конструктора Array с одним аргументом создает не массив из 20 элементов, а массив, у которого длина равна 20. А метод map сначала создает пустой массив у которого длина равна длине переданного массива, а потом вызывает коллбэк для каждого элемента переданного массива. В нашем случае элементов у массива нет и метод возвращает пустой массив такой же длины как и у исходного.


5. Finita la comedia

isFinite(42);
isFinite(1/0);
isFinite(0/0);
isFinite('42');
isFinite('hi');
isFinite();
isFinite(undefined);
isFinite(null);

Скрытый текст
isFinite(42); // true
isFinite(1/0); // false
isFinite(0/0); // NaN is not finite -> false
isFinite('42'); // true
isFinite('hi'); // false
isFinite(); // false
isFinite(undefined); // false
isFinite(null); // true

isFinite приводит аргумент к числу, и если получилось NaN, +Infinity или -Infinity, то возвращает false. Во всех остальных случаях true.
ToNumber от 'hi' вернет NaN, undefined вернет NaN, а null вернет 0.


6. True story bro

'true' == true

Скрытый текст
'true' == true //false

Оператор равенства последовательно приведет к числам сначала true, потом 'true'. Получится NaN == 1, что очевидно false.


7. Ничтожество

null == false
!null

Скрытый текст
null == false // false
!null // true

С приведением null к bool все понятно. Разберемся со сравнением. комбинация из null и bool не попадает ни под один из вариантов сравнение\приведения в пункте 11.9.3 спецификации и поэтому сравнение возвращает false.


8. Тестирование неопределенности

/^[a-z]{1,10}$/.test(null);
/^[a-z]{1,10}$/.test(undefined);

Скрытый текст
/^[a-z]{1,10}$/.test(null);  //true
/^[a-z]{1,10}$/.test(undefined);  //true

Null и undefined приводятся к строке как есть: «null» и «undefined» — а такие строки удовлетворяют регулярному выражению. И NaN, кстати, тоже станет «NaN».


9. Отрицание нуля

 0 === -0
 1/0 === 1/-0

Скрытый текст
 0 === -0        //true
 1/0 === 1/-0    //false

Нуль равен отрицательному нулю, но при делении со знаком знак учитывается и получается
Infinity === -Infinity



Для тех героев кто дочитал до конца, пожалуй, самая интересная задачка. Не хочу прятать объяснение под еще один спойлер, поэтому советую после того как вы дадите ответ, открыть devtools и проверить там. Вполне вероятно результат вас удивит и вы захотите подумать еще раз.
10. Косая черта

n = 1
/1*"\/\//.test(n + '"//')

n = 1;
/1*"\/\//.test(n + '"//');

Скрытый текст
n = 1
/1*"\/\//.test(n + '"//')  //NaN

n = 1;
/1*"\/\//.test(n + '"//');  //true

Косая черта может использоваться в трех случаях: регулярное выражение, деление и комментарий. Здесь, на первый взгляд, мы видим только регулярное выражение. Но, на самом деле, без точки с запятой первая косая черта выполняет деление. Таким образом сначала выполняется деление, а потом полученный результат умножается на строку. Оставшийся хвост остается просто комментарием. Строка на которую умножаем к числу не приводится и получается NaN.
Tags:
Hubs:
+38
Comments 17
Comments Comments 17

Articles