Замените группу захвата Regex заглавными буквами в Javascript

81

Я хотел бы знать, как заменить группу захвата прописными буквами в JavaScript. Вот упрощенная версия того, что я пробовал до сих пор, но не работает:

> a="foobar"
'foobar'
> a.replace( /(f)/, "$1".toUpperCase() )
'foobar'
> a.replace( /(f)/, String.prototype.toUpperCase.apply("$1") )
'foobar'

Не могли бы вы объяснить, что не так с этим кодом?

Эван Кэрролл
источник
@Erik не удаляет компонент вопроса. Я тоже хочу знать, почему мой код не работает.
Эван Кэрролл,
Эван, я думал, что уважаю твой вопрос. Я удалял только то, что казалось ненужным. Поскольку вы дали код, который пытались, и он, очевидно, не работал, люди неявно знали, что вам нужно объяснение, почему, без необходимости говорить об этом (и неловко). Просто пытаюсь помочь! :)
ErikE
1
Эван, так лучше? Я не хочу раздражать. Если вы откатитесь снова, я больше не буду редактировать, но не могли бы вы хотя бы сохранить изменения заголовка и тега?
ErikE
Технически я вообще не использую Javascript, я использую v8 (ECMAScript). Но я полагаю, что большинство людей, ищущих это, будут искать JavaScript, так что я хорошо с ним.
Эван Кэрролл,
1
Не стесняйтесь добавлять теги обратно, если считаете, что они принадлежат.
ErikE

Ответы:

159

Вы можете передать функцию в replace.

var r = a.replace(/(f)/, function(v) { return v.toUpperCase(); });

Объяснение

a.replace( /(f)/, "$1".toUpperCase())

В этом примере вы передаете строку функции замены. Поскольку вы используете специальный синтаксис замены ($ N захватывает N-й захват), вы просто задаете то же значение. На toUpperCaseсамом деле это обман, потому что вы делаете только верхний регистр строки замены (что несколько бессмысленно, потому что символы $и один 1не имеют верхнего регистра, поэтому возвращаемое значение все равно будет "$1") .

a.replace( /(f)/, String.prototype.toUpperCase.apply("$1"))

Вы не поверите, но семантика этого выражения абсолютно одинакова.

ХаосПандион
источник
@ Эван Кэрролл: Пожалуйста, посмотрите мой ответ.
kay
4
Ах, я понимаю, что вы имеете в виду, я перебираю "\ $ 1". Не результат колдовства, которое заменит, очевидно, заменяет $1первую группу захвата.
Эван Кэрролл,
@EvanCarroll для подробного объяснения того, почему ваш исходный код не работал и как заставить его работать, см. Мой ответ ниже.
Джошуа Пиккари
15

Я знаю, что опаздываю на вечеринку, но вот более короткий метод, который больше соответствует вашим первоначальным попыткам.

a.replace('f', String.call.bind(a.toUpperCase));

Так в чем же вы ошиблись и что это за новое вуду?

Проблема 1

Как указывалось ранее, вы пытались передать результаты вызываемого метода в качестве второго параметра String.prototype.replace () , тогда как вместо этого вы должны передать ссылку на функцию

Решение 1

Это достаточно легко решить. Простое удаление параметров и скобок даст нам ссылку, а не выполнение функции.

a.replace('f', String.prototype.toUpperCase.apply)

Проблема 2

Если вы попытаетесь запустить код сейчас, вы получите сообщение об ошибке, в котором говорится, что undefined не является функцией и поэтому не может быть вызван. Это связано с тем, что String.prototype.toUpperCase.apply на самом деле является ссылкой на Function.prototype.apply () через прототипное наследование JavaScript. То, что мы делаем на самом деле, выглядит примерно так

a.replace('f', Function.prototype.apply)

Это явно не то, что мы планировали. Как он узнает, что нужно запустить Function.prototype.apply () в String.prototype.toUpperCase () ?

Решение 2

Используя Function.prototype.bind (), мы можем создать копию Function.prototype.call со специально установленным контекстом String.prototype.toUpperCase. Теперь у нас есть следующие

a.replace('f', Function.prototype.apply.bind(String.prototype.toUpperCase))

Проблема 3

Последняя проблема заключается в том, что String.prototype.replace () передаст несколько аргументов своей функции замены. Однако Function.prototype.apply () ожидает, что второй параметр будет массивом, но вместо этого получает либо строку, либо число (в зависимости от того, используете ли вы группы захвата или нет). Это вызовет ошибку списка недопустимых аргументов.

Решение 3

К счастью, мы можем просто заменить в Function.prototype.call () (которая принимает любое количество аргументов, ни один из которых не имеет ограничений типа) на Function.prototype.apply () . Мы подошли к рабочему коду!

a.replace(/f/, Function.prototype.call.bind(String.prototype.toUpperCase))

Сбрасывание байтов!

Никто не хочет набирать прототип много раз. Вместо этого мы воспользуемся тем фактом, что у нас есть объекты, которые ссылаются на одни и те же методы через наследование. Конструктор String, будучи функцией, наследуется от прототипа функции. Это означает, что мы можем заменить в String.call Function.prototype.call (на самом деле мы можем использовать Date.call, чтобы сохранить еще больше байтов, но это менее семантично).

Мы также можем использовать нашу переменную 'a', поскольку ее прототип включает ссылку на String.prototype.toUpperCase, мы можем заменить ее на a.toUpperCase. Это комбинация трех вышеперечисленных решений и этих мер по экономии байтов - вот как мы получаем код в верхней части этого поста.

Джошуа Пиккари
источник
4
Вы сохранили 8 символов, скрывая при этом код таким образом, чтобы потребовалась страница объяснения по сравнению с более очевидным решением. Я не уверен, что это победа.
Лоуренс Дол
1
В интеллектуальном плане это отличное решение, так как оно раскрывает / учит кое-чему о функциях javascript. Но я согласен с Лоуренсом, что на практике это слишком непонятно, чтобы его можно было использовать. Все еще круто.
meetamit
9

Старый пост, но стоит расширить ответ @ChaosPandion для других случаев использования с более ограниченным RegEx. Например, убедитесь, что (f)или группа захвата окружает определенным форматом /z(f)oo/:

> a="foobazfoobar"
'foobazfoobar'
> a.replace(/z(f)oo/, function($0,$1) {return $0.replace($1, $1.toUpperCase());})
'foobazFoobar'
// Improve the RegEx so `(f)` will only get replaced when it begins with a dot or new line, etc.

Я просто хочу выделить два параметра, functionпозволяющих найти конкретный формат и заменить группу захвата в этом формате.

CallMeLaNN
источник
Спасибо! Предыдущие сообщения, похоже, ответили на проблему, почему код OP не работал, при этом полностью пропустив то, что мне казалось реальным - замену группы совпадений!
Auspex
Я думаю, у вас есть ошибка в функции замены, но проверьте меня по этому поводу. Думаю, так и должно быть return $0.replace($0, $1.toUpperCase()), где $0первый аргумент
Майк
Это была простая строка для замены. так что от f до F правильно.
CallMeLaNN
Это действительно полезно, если вы пытаетесь заменить что-то в скобках!
Diode Dan
3

Почему бы нам просто не посмотреть определение ?

Если мы напишем:

a.replace(/(f)/, x => x.toUpperCase())

мы могли бы просто сказать:

a.replace('f','F')

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

function replacer(match, p1, p2, p3, offset, string)

Если вы хотите использовать обозначение стрелочной функции:

a.replace(/xxx(yyy)zzz/, (match, p1) => p1.toUpperCase()
Бернхард Вагнер
источник
1

РЕШЕНИЕ

a.replace(/(f)/,x=>x.toUpperCase())  

для замены всех вхождений группы используйте /(f)/gregexp. Проблема в вашем коде: String.prototype.toUpperCase.apply("$1")и "$1".toUpperCase()дает "$1"(попробуйте в консоли самостоятельно) - поэтому он ничего не меняет, и на самом деле вы вызываете дважды a.replace( /(f)/, "$1")(что также ничего не меняет).

Камил Келчевски
источник
0

Учитывая словарь (объект, в данном случае a Map) свойств, значений и использование, .bind()как описано в ответах

const regex = /([A-z0-9]+)/;
const dictionary = new Map([["hello", 123]]); 
let str = "hello";
str = str.replace(regex, dictionary.get.bind(dictionary));

console.log(str);

Использование простого объекта JavaScript и с функцией, определенной для возврата совпадающего значения свойства объекта, или исходной строки, если совпадение не найдено

const regex = /([A-z0-9]+)/;
const dictionary = {
  "hello": 123,
  [Symbol("dictionary")](prop) {
    return this[prop] || prop
  }
};
let str = "hello";
str = str.replace(regex, dictionary[Object.getOwnPropertySymbols(dictionary)[0]].bind(dictionary));

console.log(str);

гость271314
источник