JavaScript: includes vs indexOf

    Начиная с ECMAScript 2016 в JavaScript появился новый метод includes для работы с массивами. По своей сути он очень сильно напоминает indexOf. В этой статье я хочу рассмотреть подробнее для чего был введен этод метод и в чем его отличие от indexOf.

    image

    Массивы


    Итак, метод Array.prototype.includes определяет содержится ли в массиве искомое значение и возвращает true или false. Таким образом, в отличие от indexOf, который возвращает целое число, includes возвращает значение типа boolean. Это нововведение поможет разработчикам писать более чистый и понятный код.

    Например, вот стандартный пример проверки того, содержится ли элемент в массиве, с помощью indexOf:

    var numbers = [3, 5, 8, 11, 23, 7];
    
    if (numbers.indexOf(1) !== -1) {
        // ...
    }
    

    Используя includes, то же самое можно написать так:

    var numbers = [3, 5, 8, 11, 23, 7];
    
    if (numbers.includes(1)) {
        // ...
    }
    

    Также при внедрении этого метода были учтены некоторые неочевидные особенности, замеченные при работе с indexOf. В отношении значений NaN поведение этого метода отличается.

    Рассмотрим на примере:

    var numbers = [3, 5, 8, 11, 23, 7, NaN];
    if (numbers.indexOf(NaN) !== -1) {
        // Этот код не выполнится
    }
    
    if (numbers.includes(NaN)) {
        // Этот код выполнится
    }
    

    Таким образом, indexOf(NaN) всегда возвращает -1, независимо от того содержится ли это значение в массиве, а includes(NaN) возвращает true или false в зависимости от того есть этот элемен в массиве или нет.

    Производительность


    Оценка производительности javascript методов не такая уж очевидная вешь, так как по сути в разных браузерах одна и та же функция может быть реализована по-разному в зависимости от языка, на котором написан сам браузер. К тому же очень много зависит от компьютера, на котором делается эта оценка. Но, тем не менее, я попытался сделать небольшой анализ.

    Я создал массив из 10000 целых положительных чисел и использовал для анализа сайт jsbench.github.io. В обоих случаях, для чистоты эксперимента, был использован один и тот же массив. Оценка производилась в браузерах Chrome 53 и Firefox 48.

    Chrome
    includes indexOf
    элемент есть
    в середине массива
    8,361 ops/sec ±0.38% 31,296 ops/sec ±0.65%
    элемент есть
    в начале массива
    22,043,904 ops/sec ±1.89% 136,512,737 ops/sec ±2.06%
    искомого эелемента
    нет в массиве
    4,018 ops/sec ±0.71% 95,221 ops/sec ±0.53%

    Firefox
    includes indexOf
    элемент есть
    в середине массива
    84,880 ops/sec ±0.59% 86,612 ops/sec ±1.35%
    элемент есть
    в начале массива
    34,087,623 ops/sec ±0.99% 33,196,839 ops/sec ±0.84%
    искомого эелемента
    нет в массиве
    25,253 ops/sec ±2.75% 14,994 ops/sec ±1.16%

    Получается, что в Chrome indexOf всегда работает быстрее, а в Firefox ощутимой разницы практически нет (кроме случая, когда в массиве нет искомого элемента). И поведение нового метода в Firefox кажется более логичным, так как в общем-то indexOf и includes по логике должны иметь одну и ту же вычислительную сложность.

    Строки


    Аналогичный метод был добавлен и для работы со строками начиная с ECMAScript 2015. Ранее в Firefox в версиях с 18 по 39 этот метод существовал под именем contains, однако из-за проблем совместимости он был переименован в includes().

    В заключение следует отметить, что данный метод поддерживается пока что не всеми браузерами.
    Браузер Массив Строки
    Chrome 47 41
    Firefox 43 40
    IE нет нет
    Opera 34 нет
    Safari 9 9
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну, и что?
    Реклама
    Комментарии 25
    • +1

      Вариация по теме:


      var numbers = [3, 5, 8, 11, 23, 7];
      
      if ( ~numbers.indexOf(1) ) {
          // ...
      }
      • +1

        За что минусуют? Вполне читаемый вариант. Главное, чтобы в проекте везде использовался один способ.

        • +4

          За то, что это использование оператора не по назначению. Ничем не лучше


          if (numbers.indexOf(x) + 1) {
            //...
          }
          • 0
            Кстати, тоже неплохо (имхо).
            • 0

              Писать что угодно, лишь бы не очевидный numbers.indexOf(x) > -1? Такая логика что ли?

              • +2
                Я о том, что он тоже вполне читаемый, хоть и выглядит странно и непривычно.

                Вариант же с ~ мне нравится тем, что он короткий. Если это принято на уровне всего проекта, на уровне кодстайла, и при этом есть оговорка, что ~ используется только с indexOf без вложенности, и не используется в более сложных конструкциях (~arr.indexOf(x) можно, а ~(тут_какое-то_очень_сложное_условие_которое_может_вернуть_-1) — нельзя), то это не должно привести к ухудшению читаемости.
      • 0
        > так как в общем-то indexOf и includes по логике должны иметь одну и ту же вычислительную сложность.

        Сложность у них разная будет, как ни крути. Вы же сами выше писали, что indexOf не правильно работает с NaN.

        Для исправления этого огреха с NaN необходимо проверять каждый элемент массива на isNaN, при этом нужно будет проверить еще и тип элемента, т.к. isNaN('a') == true.

        В то время когда Array.indexOf будет сравнивать по ссылке, а в некоторых случаях еще и по значению.
        • 0
          Угу. И судя по результатам, такая проверка у Firefox была (есть) и у indexOf.
          • +1
            Согласен с этим. Но меня удивило, что иногда результаты тестов отличаются на порядок.
            • +1

              Не удивляйтесь. В погоне за фичами, сейчас все разработчики движков сначала их делают, иногда даже прямо на js, и только потом оптимизируют.

              • +1
                Надо смотреть код теста, если вы написали что-то типа:
                /* Array#indexOf */
                array.indexOf(XXX)
                
                /* Array#includes */
                array.includes(XXX)
                


                То может смело выкинуть результаты.
              • 0

                Строго говоря, нет. Различие только в методе сравнения: indexOf производит Strict Equality Comparison, а includes — SameValueZero. Видимо, всё дело в оптимизации.

                • 0
                  Спасибо за ссылки! Жаль вторая не работает, но первая ответила на вопрос, который меня давно интересовал, про то, как сравниваются строки.
                  • 0

                    Проверил ссылки — вроде обе корректно работают. И при чём здесь строки?

                    • 0
                      Да, обе корректно отработали, видать в первый раз сильно долго грузилась страница, переход на якорь не сработал.
                      На счет строк — это оффтоп.
                      • 0

                        Простите, вы никогда не заглядывали в спецификацию экмаскрипта?

                        • 0
                          Заглядывал, некоторые моменты нужно было прояснить, но не изучал. Пока что не сильно плотно с js работаю.
              • +5
                Не знаю какие тесты были автора, но начав писать микробенчмарки вы ступаете на очень скользкую дорожку, а результаты могут быть в корне неверными.

                Например, можно написать вот такой тест:
                http://jsbench.github.io/#0c02e5ebe25ee0374e3736f3289e922a
                Array#includes — WIN!

                Или такой (вначале, середине и не найден):
                http://jsbench.github.io/#bc19a03c9aeea1dc1f44731396c05396
                Ну, можно сказать, что Array#indexOf иногда выигрывает, но в пределах нормы

                Так что используйте Array#includes, мифический выигрыш не стоит ухудшения читаемости.
                • 0
                  В принципе согласен. Я когда делал тесты, то тоже был уверен, что это не прям отличный показатель производительности. Но я запускал тесты несколько раз и смотрел на общую картину. Тоже считаю, что includes() более правильный метод, если речь идет именно о том содержится ли элемент в массиве. Единственное «но» здесь как обычно — это, то что не во всех браузерах работает. Хотя на сайте ECMAScript предлагается решение этой проблемы
                  • –4

                    Ухудшения читаемости не происходит. "Программисты", не знакомые с побитовыми операциями мне не друзья.

                    • +1

                      Ясно, понятно.

                      • +1

                        "Знаком с побитовыми операциями" !== "умеет читать побитовые операции быстро".

                        • +1

                          Эй, ну зачем вы так парня минусует, ну горяч, категоричен, бывает. Несколько лет назад тоже иногда так писал, а потом устал и начал более выразительно свою мысль доносить.

                          • +3

                            Затем и минусую, чтобы был стимул доносить свою мысль выразительнее.

                      • 0
                        Вижу бенчмарки в JS, вспоминаю mraleph: https://www.youtube.com/watch?v=HPFARivHJRY
                        Вижу бенчмарки в Java, вспоминаю @shipilev

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

                        Самое читаемое