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


    После новогодних праздников я уже предлагал сообществу размяться головоломными ситуациями в 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.

    Similar posts

    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 17

      +3
      Вот репозиторий с аналогичными головоломками: github.com/miguelmota/javascript-idiosyncrasies
        +2
        Вот еще загадка (заставила поматюкаться сегодня):

        ( 0.045 ).toFixed( 2 ) == ( 0.035 ).toFixed( 2 )
        
          +1
          округление к ближайшему четному называется.
            0
            А тогда почему
            ( 0.045 ).toFixed( 2 ) // 0.04
            и
            ( 0.065 ).toFixed( 2 ) // 0.07
            ?
              +1
              Числа хранятся согласно IEEE 754. Об этом не раз уже говорилось на хабре.
                +1
                Нда, я забыл, это ж JS.
                Тут просто «нативное» округление.
                Ради интересу взгляните на
                (0.005).toFixed(20)
                (0.015).toFixed(20)
                (0.025).toFixed(20)
                (0.035).toFixed(20)
                (0.045).toFixed(20)
                (0.055).toFixed(20)
              0
              а вы вот этим её

              var n = 100;
              ( Math.ceil( 0.045 * n ) / n ).toFixed( 2 ) == ( Math.ceil( 0.035 * n ) / n ).toFixed( 2 )
              
            • UFO just landed and posted this here
                +4
                Экспериментируя с +- из первого примера получил

                "3" +-+ "1" // = "3-1"

                В принципе понятно почему (= «3» + -1 = «3-1»), но выглядит еще разрушительнее.
                  +7
                  Стесняюсь спросить, это вы просто wtfjs.com/ пересказали?
                    +4
                    Большая часть примеров оттуда. Я этого не скрываю и даже в метках указал. В качестве цели статьи я ставил разбор логики поведения. По-моему, простой просмотр «смотри какая хрень в JavaScript» не несет пользы, полезно понять почему именно так.
                    –2
                    Еще загадка JavaScript:
                    0.57*100

                    Сколько получится?
                      0
                      вам тоже смотреть на
                      (0.57).toFixed(20)
                        +1
                        Отсюда красиво вот это получается:
                        a = 0.57
                        b = 1
                        
                        a + b - b == a
                        false
                        
                        a - b + b == a
                        true
                        
                        +2
                        Оу, приходите к нам в гости, мы на работе иногда так развлекаемся командой. Делаем встречи в стиле FunJS.
                        У нас уже и своих загадок набралась куча. И, кстати, не только в JS.
                          0
                          В 10 задачке Хабрапарсер мне подсказал ответ (я читер).
                            0
                            По подсветке синтаксиса выглядит так, как-будто никакого деления нет, есть умножение 1-цы на строку.

                            Only users with full accounts can post comments. Log in, please.