После новогодних праздников я уже предлагал сообществу размяться головоломными ситуациями в JavaScript. С того поста прошло порядочно времени, было много других праздников, так что предлагаю подумать над новой порцией задачек.
Ответы и свой вариант объяснения почему такое поведение логично, как и в прошлый раз, я буду скрывать под спойлером. Сразу оговорюсь, что не претендую на непоколебимую истину своих версий и буду рад их обсудить. В разгадывании вам может помочь отличный русский перевод спецификации ECMAScript 5, за который большое спасибо iliakan!
1. Главный вопрос жизни, вселенной и всего такого
"3" -+-+-+ "1" + "1" / "3" * "6" + "2"
Решение
Как мы уже знаем из прошлого поста оператор “+” выполняет либо конкатенацию строк, либо сложение, либо приведение к числу. Т.е.
а не 5, как можно было бы подумать.
Кроме этого, вы можете бросить унарный оператор “+” или “-” на выражение, чтобы изменить его знак и сделать вещи красивее и читабельнее.
С учетом всего вышесказанного:
Дальше вспомним приоритет операций: / * +
"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);
Решение
Тут все просто.
попытаемся выполнить toNumber для {}:
выполним valueOf и проверим примитив ли это — нет, тогда
выполним toString и получим "[object Object]";
выполним для получившейся строки toNumber и получим NaN;
в случае получения NaN Math.max() всегда возвращает NaN.
попытаемся выполнить toNumber для []:
выполним valueOf и проверим примитив ли это — нет, тогда
выполним toString и получим "";
выполним для получившейся строки toNumber и получим 0;
3 > 0
попытаемся выполнить toNumber для [1]:
выполним valueOf и проверим примитив ли это — нет, тогда
выполним toString и получим «1»;
выполним для получившейся строки toNumber и получим 1;
-1 < 1
попытаемся выполнить toNumber для [1,4]:
выполним valueOf и проверим примитив ли это — нет, тогда
выполним toString и получим «1,4»;
выполним для получившейся строки toNumber и получим NaN;
в случае получения NaN Math.max() всегда возвращает NaN.
попытаемся выполнить toNumber для true:
выполним valueOf и проверим примитив ли это — да, тогда
3 > 1
для 'foo' toNumber возвращает NaN.
В случае получения NaN Math.max() всегда возвращает NaN.
в случае получения NaN Math.max() всегда возвращает NaN.
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()
Скрытый текст
Тут я могу сослатся только на Флэнагана. 7.1:
«Если литерал массива содержит несколько идущих подряд запятых без значений между ними, создается разреженный массив. Элементы, соответствующие таким пропущенным значениям, отсутствуют в массиве, но при обращении к ним возвращается значение undefined.»
«Синтаксис литералов массивов позволяет вставлять необязательную завершающую запятую.»
Зачем так сделано мне не понятно. В комментариях к прошлому посту это преподносилось как фича. По моему это все таки браузерный костыль от ошибок в коде.
[,,,].join() // ",,"
[,,,undefined].join() // ",,,"
Тут я могу сослатся только на Флэнагана. 7.1:
«Если литерал массива содержит несколько идущих подряд запятых без значений между ними, создается разреженный массив. Элементы, соответствующие таким пропущенным значениям, отсутствуют в массиве, но при обращении к ним возвращается значение undefined.»
«Синтаксис литералов массивов позволяет вставлять необязательную завершающую запятую.»
Зачем так сделано мне не понятно. В комментариях к прошлому посту это преподносилось как фича. По моему это все таки браузерный костыль от ошибок в коде.
4. Карта сокровищ
Array(20).map(function(elem) { return 'a'; });
Скрытый текст
Вызов конструктора Array с одним аргументом создает не массив из 20 элементов, а массив, у которого длина равна 20. А метод map сначала создает пустой массив у которого длина равна длине переданного массива, а потом вызывает коллбэк для каждого элемента переданного массива. В нашем случае элементов у массива нет и метод возвращает пустой массив такой же длины как и у исходного.
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 приводит аргумент к числу, и если получилось NaN, +Infinity или -Infinity, то возвращает false. Во всех остальных случаях true.
ToNumber от 'hi' вернет NaN, undefined вернет NaN, а null вернет 0.
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'. Получится NaN == 1, что очевидно false.
'true' == true //false
Оператор равенства последовательно приведет к числам сначала true, потом 'true'. Получится NaN == 1, что очевидно false.
7. Ничтожество
null == false
!null
Скрытый текст
С приведением null к bool все понятно. Разберемся со сравнением. комбинация из null и bool не попадает ни под один из вариантов сравнение\приведения в пункте 11.9.3 спецификации и поэтому сравнение возвращает false.
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);
Скрытый текст
Null и undefined приводятся к строке как есть: «null» и «undefined» — а такие строки удовлетворяют регулярному выражению. И NaN, кстати, тоже станет «NaN».
/^[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 + '"//');
Скрытый текст
Косая черта может использоваться в трех случаях: регулярное выражение, деление и комментарий. Здесь, на первый взгляд, мы видим только регулярное выражение. Но, на самом деле, без точки с запятой первая косая черта выполняет деление. Таким образом сначала выполняется деление, а потом полученный результат умножается на строку. Оставшийся хвост остается просто комментарием. Строка на которую умножаем к числу не приводится и получается NaN.
n = 1
/1*"\/\//.test(n + '"//') //NaN
n = 1;
/1*"\/\//.test(n + '"//'); //true
Косая черта может использоваться в трех случаях: регулярное выражение, деление и комментарий. Здесь, на первый взгляд, мы видим только регулярное выражение. Но, на самом деле, без точки с запятой первая косая черта выполняет деление. Таким образом сначала выполняется деление, а потом полученный результат умножается на строку. Оставшийся хвост остается просто комментарием. Строка на которую умножаем к числу не приводится и получается NaN.