Замените группу захвата регулярных выражений на верхний регистр в Javascript

#javascript #regex #replace #uppercase

#javascript #регулярное выражение #замените #верхний регистр

Вопрос:

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

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

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

Комментарии:

1. @Erik не удаляйте компонент вопроса. Я тоже хочу знать, почему мой код дает сбой.

2. Эван, я думал, что отнесся с уважением к твоему вопросу. Я удалил только то, что казалось ненужным. Поскольку вы предоставили код, который вы пробовали, и он, очевидно, не работал, тогда люди неявно знали, что вам нужно объяснение, почему, без того, чтобы вам пришлось говорить об этом (и неловко). Просто пытаюсь помочь! 🙂

3. Эван, так лучше? Я не хочу раздражать. Если вы снова выполните откат, я больше не буду редактировать, но не могли бы вы, по крайней мере, сохранить изменения заголовка и тега на месте?

4. Не стесняйтесь добавлять теги обратно, если вы считаете, что они принадлежат.

5. Сбивающим с толку аспектом этого вопроса для меня было то, что вопрос является частным случаем, когда группа захвата также является всем согласованным выражением. Если это не так, то ответы дают неожиданные результаты, потому что они касаются всего совпадения.

Ответ №1:

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

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

Объяснение

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

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

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

Хотите верьте, хотите нет, но семантика этого выражения в точности такая же.

Комментарии:

1. @Эван Кэрролл: Пожалуйста, посмотрите мой ответ.

2. А, я понимаю, что вы имеете в виду, я увеличиваю значение » $ 1″. Не результат действия voodoo, который replace сделает, это, по-видимому, замена $1 первой группы захвата.

3. @EvanCarroll подробное объяснение того, почему ваш первоначальный код не работал и как заставить его работать, смотрите в моем ответе ниже.

4.В целом несущественно, но группа захвата не нужна, f (f) поскольку v ссылается на группу 0. Обратите внимание на людей, читающих это. В быстром тестировании каждая группа захвата замедляла регулярное выражение на 5%.

Ответ №2:

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

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

Итак, где вы ошиблись и что это за новый voodoo?

Проблема 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))
  

Потеря байтов!

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

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

Комментарии:

1. Вы сохранили 8 символов, скрыв при этом код таким образом, чтобы потребовалась страница объяснения более очевидного решения. Я не уверен, что это выигрыш.

2. С интеллектуальной точки зрения, это отличное решение, поскольку оно раскрывает / обучает кое-чему о функциях javascript. Но я согласен с Лоуренсом в том, что на практике это слишком неясно, чтобы действительно использоваться. Все еще круто.

3. Да, не хочу видеть подобный код в продакшене, но было действительно весело, когда я сам узнал, что это можно сделать в JS : D

Ответ №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. ИМХО, это самое простое и элегантное решение.

Ответ №4:

Старый пост, но стоит расширить ответ @ChaosPandion для других вариантов использования с более ограниченным регулярным выражением. Например. убедитесь, что (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 , которые позволяют находить определенный формат и заменять группу захвата в пределах формата.

Комментарии:

1. Спасибо! Предыдущие сообщения, похоже, ответили на проблему, почему код OP не работал, при этом полностью пропуская то, что казалось мне реальным моментом — замена группы совпадений!

2. Я думаю, что у вас ошибка в вашей функции replace, но проверьте меня на этот счет. Я думаю, что это должно быть return $0.replace($0, $1.toUpperCase()) , где $0 является первым аргументом

3. Это была простая строка для замены строки. итак, от f до F правильно.

4. Это действительно полезно, если вы пытаетесь заменить что-то в скобках!

Ответ №5:

РЕШЕНИЕ

 a.replace(/(f)/,(m,g)=>g.toUpperCase())  
  

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

 let a= "foobar";
let b= a.replace(/(f)/,(m,g)=>g.toUpperCase());
let c= a.replace(/(o)/g,(m,g)=>g.toUpperCase());

console.log("/(f)/ ", b);
console.log("/(o)/g", c);  

Комментарии:

1. Что такое m и g ? Я думаю, g это group ?

2. @mikemaccana m означает matched substring — это аргумент для стандартизированной функции-заменителя

Ответ №6:

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

 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);  

Ответ №7:

В случае преобразования строки из camelCase в bash_case (т.Е. для имен файлов) используйте обратный вызов с тернарным оператором.

Захваченная группа, выбранная с помощью регулярного выражения () в первом (левом) аргументе замены, отправляется во второй (правый) аргумент, который является функцией обратного вызова. x и y приведите захваченную строку (не знаю, почему 2 раза!), а индекс (третий) дает индекс начала захваченной группы в строке ссылки. Для этого можно использовать тернарный оператор, который не помещается _ при первом появлении.

 let str = 'MyStringName';
str = str.replace(/([^a-z0-9])/g, (x,y,index) => {
      return index != 0 ? '_'   x.toLowerCase() : x.toLowerCase();
});
console.log(str);