Поиск подстроки
Существует несколько способов поиска подстроки.
str.indexOf
Первый метод — str.indexOf(substr, pos).
Он ищет подстроку substr
в строке str
, начиная с позиции pos
, и возвращает позицию, на которой располагается совпадение, либо -1
при отсутствии совпадений.
Например:
let str = 'Widget with id';
alert( str.indexOf('Widget') ); // 0, потому что подстрока 'Widget' найдена в начале
alert( str.indexOf('widget') ); // -1, совпадений нет, поиск чувствителен к регистру
alert( str.indexOf("id") ); // 1, подстрока "id" найдена на позиции 1 (..idget with id)
Необязательный второй аргумент позволяет начать поиск с определённой позиции.
Например, первое вхождение "id"
— на позиции 1
. Для того, чтобы найти следующее, начнём поиск с позиции 2
:
let str = 'Widget with id';
alert( str.indexOf('id', 2) ) // 12
Чтобы найти все вхождения подстроки, нужно запустить indexOf
в цикле. Каждый раз, получив очередную позицию, начинаем новый поиск со следующей:
let str = 'Ослик Иа-Иа посмотрел на виадук';
let target = 'Иа'; // цель поиска
let pos = 0;
while (true) {
let foundPos = str.indexOf(target, pos);
if (foundPos == -1) break;
alert( `Найдено тут: ${foundPos}` );
pos = foundPos + 1; // продолжаем со следующей позиции
}
Тот же алгоритм можно записать и короче:
let str = "Ослик Иа-Иа посмотрел на виадук";
let target = "Иа";
let pos = -1;
while ((pos = str.indexOf(target, pos + 1)) != -1) {
alert( pos );
}
str.lastIndexOf(substr, position)
Также есть похожий метод str.lastIndexOf(substr, position), который ищет с конца строки к её началу.
Он используется тогда, когда нужно получить самое последнее вхождение: перед концом строки или начинающееся до (включительно) определённой позиции.
При проверке indexOf
в условии if
есть небольшое неудобство. Такое условие не будет работать:
let str = "Widget with id";
if (str.indexOf("Widget")) {
alert("Совпадение есть"); // не работает
}
Мы ищем подстроку "Widget"
, и она здесь есть, прямо на позиции 0
. Но alert
не показывается, т. к. str.indexOf("Widget")
возвращает 0
, и if
решает, что тест не пройден.
Поэтому надо делать проверку на -1
:
let str = "Widget with id";
if (str.indexOf("Widget") != -1) {
alert("Совпадение есть"); // теперь работает
}
Трюк с побитовым НЕ
Существует старый трюк с использованием побитового оператора НЕ — ~
. Он преобразует число в 32-разрядное целое со знаком (signed 32-bit integer). Дробная часть, в случае, если она присутствует, отбрасывается. Затем все биты числа инвертируются.
На практике это означает простую вещь: для 32-разрядных целых чисел значение ~n
равно -(n+1)
.
В частности:
alert( ~2 ); // -3, то же, что -(2+1)
alert( ~1 ); // -2, то же, что -(1+1)
alert( ~0 ); // -1, то же, что -(0+1)
alert( ~-1 ); // 0, то же, что -(-1+1)
Таким образом, ~n
равняется 0 только при n == -1
(для любого n
, входящего в 32-разрядные целые числа со знаком).
Соответственно, прохождение проверки if ( ~str.indexOf("…") )
означает, что результат indexOf
отличен от -1
, совпадение есть.
Это иногда применяют, чтобы сделать проверку indexOf
компактнее:
let str = "Widget";
if (~str.indexOf("Widget")) {
alert( 'Совпадение есть' ); // работает
}
Обычно использовать возможности языка каким-либо неочевидным образом не рекомендуется, но этот трюк широко используется в старом коде, поэтому его важно понимать.
Просто запомните: if (~str.indexOf(…))
означает «если найдено».
Впрочем, если быть точнее, из-за того, что большие числа обрезаются до 32 битов оператором ~
, существуют другие числа, для которых результат тоже будет 0
, самое маленькое из которых — ~4294967295=0
. Поэтому такая проверка будет правильно работать только для строк меньшей длины.
На данный момент такой трюк можно встретить только в старом коде, потому что в новом он просто не нужен: есть метод .includes
(см. ниже).
includes, startsWith, endsWith
Более современный метод str.includes(substr, pos) возвращает true
, если в строке str
есть подстрока substr
, либо false
, если нет.
Это — правильный выбор, если нам необходимо проверить, есть ли совпадение, но позиция не нужна:
alert( "Widget with id".includes("Widget") ); // true
alert( "Hello".includes("Bye") ); // false
Необязательный второй аргумент str.includes
позволяет начать поиск с определённой позиции:
alert( "Midget".includes("id") ); // true
alert( "Midget".includes("id", 3) ); // false, поиск начат с позиции 3
Методы str.startsWith и str.endsWith проверяют, соответственно, начинается ли и заканчивается ли строка определённой строкой:
alert( "Widget".startsWith("Wid") ); // true, "Wid" — начало "Widget"
alert( "Widget".endsWith("get") ); // true, "get" — окончание "Widget"
Получение подстроки
В JavaScript есть 3 метода для получения подстроки: substring
, substr
и slice
.str.slice(start [, end])
Возвращает часть строки от start
до (не включая) end
.
Например:
let str = "stringify";
// 'strin', символы от 0 до 5 (не включая 5)
alert( str.slice(0, 5) );
// 's', от 0 до 1, не включая 1, т. е. только один символ на позиции 0
alert( str.slice(0, 1) );
Если аргумент end
отсутствует, slice
возвращает символы до конца строки:
let str = "stringify";
alert( str.slice(2) ); // ringify, с позиции 2 и до конца
Также для start/end
можно задавать отрицательные значения. Это означает, что позиция определена как заданное количество символов с конца строки:
let str = "stringify";
// начинаем с позиции 4 справа, а заканчиваем на позиции 1 справа
alert( str.slice(-4, -1) ); // gif
str.substring(start [, end])
Возвращает часть строки между start
и end
.
Это — почти то же, что и slice
, но можно задавать start
больше end
.
Например:
let str = "stringify";
// для substring эти два примера — одинаковы
alert( str.substring(2, 6) ); // "ring"
alert( str.substring(6, 2) ); // "ring"
// …но не для slice:
alert( str.slice(2, 6) ); // "ring" (то же самое)
alert( str.slice(6, 2) ); // "" (пустая строка)
Отрицательные значения substring
, в отличие от slice
, не поддерживает, они интерпретируются как 0
.str.substr(start [, length])
Возвращает часть строки от start
длины length
.
В противоположность предыдущим методам, этот позволяет указать длину вместо конечной позиции:
let str = "stringify";
// ring, получаем 4 символа, начиная с позиции 2
alert( str.substr(2, 4) );
Значение первого аргумента может быть отрицательным, тогда позиция определяется с конца:
let str = "stringify";
// gi, получаем 2 символа, начиная с позиции 4 с конца строки
alert( str.substr(-4, 2) );
Давайте подытожим, как работают эти методы, чтобы не запутаться:
метод | выбирает… | отрицательные значения |
---|---|---|
slice(start, end) | от start до end (не включая end ) | можно передавать отрицательные значения |
substring(start, end) | между start и end | отрицательные значения равнозначны 0 |
substr(start, length) | length символов, начиная от start | значение start может быть отрицательным |
Какой метод выбрать?
Все эти методы эффективно выполняют задачу. Формально у метода substr
есть небольшой недостаток: он описан не в собственно спецификации JavaScript, а в приложении к ней — Annex B. Это приложение описывает возможности языка для использования в браузерах, существующие в основном по историческим причинам. Таким образом, в другом окружении, отличном от браузера, он может не поддерживаться. Однако на практике он работает везде.
Из двух других вариантов, slice
более гибок, он поддерживает отрицательные аргументы, и его короче писать. Так что, в принципе, можно запомнить только его.