Перейти к основному содержимому

`String.prototype.replaceAll`

· 4 мин. чтения
Матиас Байненс ([@mathias](https://twitter.com/mathias))

Если вы когда-либо работали со строками в JavaScript, то, скорее всего, сталкивались с методом String#replace. String.prototype.replace(searchValue, replacement) возвращает строку с некоторыми заменёнными совпадениями в зависимости от указанных параметров:

'abc'.replace('b', '_');
// → 'a_c'

'🍏🍋🍊🍓'.replace('🍏', '🥭');
// → '🥭🍋🍊🍓'

Распространённым случаем использования является замена всех экземпляров заданной подстроки. Однако String#replace напрямую не решает эту задачу. Когда searchValue является строкой, заменяется только первое вхождение подстроки:

'aabbcc'.replace('b', '_');
// → 'aa_bcc'

'🍏🍏🍋🍋🍊🍊🍓🍓'.replace('🍏', '🥭');
// → '🥭🍏🍋🍋🍊🍊🍓🍓'

Чтобы обойти это ограничение, разработчики часто преобразуют строку поиска в регулярное выражение с глобальным флагом (g). Таким образом, метод String#replace заменяет все совпадения:

'aabbcc'.replace(/b/g, '_');
// → 'aa__cc'

'🍏🍏🍋🍋🍊🍊🍓🍓'.replace(/🍏/g, '🥭');
// → '🥭🥭🍋🍋🍊🍊🍓🍓'

Для разработчиков это неприятно, когда приходится делать преобразование строки в регулярное выражение, если вы действительно хотите выполнить замену всех совпадений подстроки. Более того, это преобразование может привести к ошибкам, и часто становится причиной багов! Рассмотрим следующий пример:

const queryString = 'q=query+string+parameters';

queryString.replace('+', ' ');
// → 'q=query string+parameters' ❌
// Только первое вхождение будет заменено.

queryString.replace(/+/, ' ');
// → SyntaxError: invalid regular expression ❌
// Оказывается, `+` — это специальный символ в шаблонах регулярных выражений.

queryString.replace(/\+/, ' ');
// → 'q=query string+parameters' ❌
// Экранирование специальных символов делает регулярное выражение допустимым, но
// при этом всё ещё заменяется только первое вхождение `+` в строке.

queryString.replace(/\+/g, ' ');
// → 'q=query string parameters' ✅
// Экранирование специальных символов И использование флага `g` делает замену рабочей.

Преобразование строкового литерала вроде '+' в глобальное регулярное выражение — это не просто вопрос избавления от кавычек ', оборачивания в наклонные слэш / и добавления флага g, — необходимо экранировать любые символы, которые имеют специальное значение в регулярных выражениях. Это легко забыть и тяжело реализовать правильно, так как JavaScript не предоставляет встроенного механизма для экранирования шаблонов регулярных выражений.

Другой способ обхода — это использование сочетания методов String#split и Array#join:

const queryString = 'q=query+string+parameters';
queryString.split('+').join(' ');
// → 'q=query string parameters'

Этот подход избегает необходимости экранирования, но сопровождается дополнительной нагрузкой — разделением строки на массив частей только для её обратного склеивания.

Очевидно, что ни один из этих обходных путей не является идеальным. Разве не было бы здорово, если бы такая базовая операция, как глобальная замена подстроки, была простой в JavaScript?

String.prototype.replaceAll

Новый метод String#replaceAll решает эти проблемы и предоставляет простой механизм для выполнения глобальной замены подстроки:

'aabbcc'.replaceAll('b', '_');
// → 'aa__cc'

'🍏🍏🍋🍋🍊🍊🍓🍓'.replaceAll('🍏', '🥭');
// → '🥭🥭🍋🍋🍊🍊🍓🍓'

const queryString = 'q=query+string+parameters';
queryString.replaceAll('+', ' ');
// → 'q=query string parameters'

Для согласованности с ранее представленными API в языке, String.prototype.replaceAll(searchValue, replacement) ведёт себя точно так же, как String.prototype.replace(searchValue, replacement), за исключением следующих двух особенностей:

  1. Если searchValue является строкой, то String#replace заменяет только первое вхождение подстроки, тогда как String#replaceAll заменяет все вхождения.
  2. Если searchValue является не глобальным регулярным выражением, то String#replace заменяет только одно совпадение, аналогично тому, как он работает для строк. String#replaceAll, с другой стороны, генерирует исключение в этом случае, так как это, вероятно, ошибка: если вы действительно хотите «заменить все» совпадения, вам следует использовать глобальное регулярное выражение; если вы хотите заменить только одно совпадение, можно использовать String#replace.

Ключевая новая функциональность заключается в первом пункте. String.prototype.replaceAll обогащает JavaScript поддержкой глобальной замены подстрок первого класса без необходимости использовать регулярные выражения или другие обходные пути.

Заметка о специальных шаблонах замены

Стоит отметить: и replace, и replaceAll поддерживают специальные шаблоны замены. Хотя они наиболее полезны в сочетании с регулярными выражениями, некоторые из них ($$, $&, $` , и $') также используются при простой замене строк, что может быть неожиданным:

'xyz'.replaceAll('y', '$$');
// → 'x$z' (не 'x$$z')

Если строка замены содержит один из этих шаблонов, а вы хотите использовать их как есть, вы можете отключить волшебное поведение замены, используя функцию-заменитель, которая возвращает строку:

'xyz'.replaceAll('y', () => '$$');
// → 'x$$z'

Поддержка String.prototype.replaceAll