Почему «asdf» .replace (/.*/ g, «x») == «xx»? - PullRequest
132 голосов
/ 17 апреля 2020

Я наткнулся на удивительный (для меня) факт.

console.log("asdf".replace(/.*/g, "x"));

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

Ответы [ 4 ]

100 голосов
/ 17 апреля 2020

Согласно стандарту ECMA-262 , String.prototype.replace вызывает RegExp.prototype [@@ replace] , что говорит:

11. Repeat, while done is false
  a. Let result be ? RegExpExec(rx, S).
  b. If result is null, set done to true.
  c. Else result is not null,
    i. Append result to the end of results.
    ii. If global is false, set done to true.
    iii. Else,
      1. Let matchStr be ? ToString(? Get(result, "0")).
      2. If matchStr is the empty String, then
        a. Let thisIndex be ? ToLength(? Get(rx, "lastIndex")).
        b. Let nextIndex be AdvanceStringIndex(S, thisIndex, fullUnicode).
        c. Perform ? Set(rx, "lastIndex", nextIndex, true).

, где rx равно /.*/g, а S равно 'asdf'.

См. 11. c .iii.2.b:

б. Пусть nextIndex будет AdvanceStringIndex (S, thisIndex, fullUnicode).

Поэтому в 'asdf'.replace(/.*/g, 'x') это фактически:

  1. result (undefined), results = [], lastIndex = 0
  2. результат = 'asdf', результаты = [ 'asdf' ], lastIndex = 4
  3. результат = '', результаты = [ 'asdf', '' ], lastIndex = 4, AdvanceStringIndex, установите lastIndex равным 5
  4. result = null, results = [ 'asdf', '' ], return

Поэтому существует 2 совпадения.

35 голосов
/ 17 апреля 2020

Вместе в автономном чате с yawkat мы нашли интуитивно понятный способ увидеть, почему "abcd".replace(/.*/g, "x") точно дает два совпадения. Обратите внимание, что мы не проверили, полностью ли он соответствует семантике, наложенной стандартом ECMAScript, поэтому просто примите это как практическое правило.

Полезные правила

  • Учитывайте совпадения в виде списка кортежей (matchStr, matchIndex) в хронологическом порядке, которые указывают, какие части строки и индексы входной строки уже были съедены.
  • Этот список непрерывно создается, начиная слева от входной строки для регулярное выражение.
  • Уже израсходованные детали больше не могут быть сопоставлены
  • Замена выполняется по индексам, заданным matchIndex перезаписью подстроки matchStr в этой позиции. Если matchStr = "", то «замена» фактически является вставкой.

Формально акт сопоставления и замены описывается как al oop, как видно в другом ответе .

Простые примеры

  1. "abcd".replace(/.*/g, "x") выходы "xx":

    • Список совпадений [("abcd", 0), ("", 4)]

      Примечательно, что не включает в себя следующие совпадения, о которых можно подумать по следующим причинам:

      • ("a", 0), ("ab", 0): квантификатор * жадный
      • ("b", 1), ("bc", 1): из-за предыдущего совпадения ("abcd", 0) строки "b" и "bc" уже съедены
      • ("", 4), ("", 4) (т.е. дважды): позиция индекса 4 уже съедена первым очевидным совпадением
    • Следовательно, замещающая строка "x" заменяет найденные строки совпадения именно в этих позициях: at в позиции 0 он заменяет строку "abcd", а в позиции 4 он заменяет "".

      Здесь вы можете видеть, что замена может действовать как истинный ответ. Ввод предыдущей строки или просто вставка новой.

  2. "abcd".replace(/.*?/g, "x") с ленивым квантификатором *? вывод "xaxbxcxdx"

    • Список совпадений [("", 0), ("", 1), ("", 2), ("", 3), ("", 4)]

      В отличие от предыдущего примера, здесь ("a", 0), ("ab", 0), ("abc", 0) или даже ("abcd", 0) не включены из-за лени квантификатора, который строго ограничивает его для нахождения кратчайшего возможного соответствия.

    • Поскольку все строки соответствия пусты, никакой фактической замены не происходит, но вместо этого вставляются x в позициях 0, 1, 2, 3 и 4.

  3. "abcd".replace(/.+?/g, "x") с ленивым квантификатором +? выходов "xxxx"

    • Список совпадений [("a", 0), ("b", 1), ("c", 2), ("d", 3)]
  4. "abcd".replace(/.{2,}?/g, "x") с ленивым квантификатором [2,}? выходы "xx"

    • Список совпадений [("ab", 0), ("cd", 2)]
  5. "abcd".replace(/.{0}/g, "x") вывод "xaxbxcxdx" по тем же логам c, что и в примере 2.

Более сложные примеры

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

  1. "abcdefgh".replace(/(?<=^(..)*)/g, "_")) с положительным взглядом позади (?<=...) выходы "_ab_cd_ef_gh_" (пока поддерживается только в Chrome)

    • Список совпадений [("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
  2. "abcdefgh".replace(/(?=(..)*$)/g, "_")) с положительный прогноз (?=...) выходы "_ab_cd_ef_gh_"

    • Список совпадений [("", 0), ("", 2), ("", 4), ("", 6), ("", 8)]
27 голосов
/ 17 апреля 2020

Первое совпадение очевидно "asdf" (позиция [0,4]). Поскольку глобальный флаг (g) установлен, поиск продолжается. В этой точке (позиция 4) он находит второе совпадение, пустую строку (позиция [4,4]).

Помните, что * соответствует нулю или большему количеству элементов.

1 голос
/ 04 мая 2020
Просто

, первый x предназначен для замены соответствующего asdf.

второго x для пустой строки после asdf. Поиск прекращается, когда пусто.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...