Replace a Regex capture group with uppercase in Javascript

一个人想着一个人 提交于 2019-11-26 15:21:21
ChaosPandion

You can pass a function to replace.

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

Explanation

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

In this example you pass a string to the replace function. Since you are using the special replace syntax ($N grabs the Nth capture) you are simply giving the same value. The toUpperCase is actually deceiving because you are only making the replace string upper case (Which is somewhat pointless because the $ and one 1 characters have no upper case so the return value will still be "$1").

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

Believe it or not the semantics of this expression are exactly the same.

I know I'm late to the party but here is a shorter method that is more along the lines of your initial attempts.

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

So where did you go wrong and what is this new voodoo?

Problem 1

As stated before, you were attempting to pass the results of a called method as the second parameter of String.prototype.replace(), when instead you ought to be passing a reference to a function

Solution 1

That's easy enough to solve. Simply removing the parameters and parentheses will give us a reference rather than executing the function.

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

Problem 2

If you attempt to run the code now you will get an error stating that undefined is not a function and therefore cannot be called. This is because String.prototype.toUpperCase.apply is actually a reference to Function.prototype.apply() via JavaScript's prototypical inheritance. So what we are actually doing looks more like this

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

Which is obviously not what we have intended. How does it know to run Function.prototype.apply() on String.prototype.toUpperCase()?

Solution 2

Using Function.prototype.bind() we can create a copy of Function.prototype.call with its context specifically set to String.prototype.toUpperCase. We now have the following

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

Problem 3

The last issue is that String.prototype.replace() will pass several arguments to its replacement function. However, Function.prototype.apply() expects the second parameter to be an array but instead gets either a string or number (depending on if you use capture groups or not). This would cause an invalid argument list error.

Solution 3

Luckily, we can simply substitute in Function.prototype.call() (which accepts any number of arguments, none of which have type restrictions) for Function.prototype.apply(). We have now arrived at working code!

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

Shedding bytes!

Nobody wants to type prototype a bunch of times. Instead we'll leverage the fact that we have objects that reference the same methods via inheritance. The String constructor, being a function, inherits from Function's prototype. This means that we can substitute in String.call for Function.prototype.call (actually we can use Date.call to save even more bytes but that's less semantic).

We can also leverage our variable 'a' since it's prototype includes a reference to String.prototype.toUpperCase we can swap that out with a.toUpperCase. It is the combination of the 3 solutions above and these byte saving measures that is how we get the code at the top of this post.

Old post but it worth to extend @ChaosPandion answer for other use cases with more restricted RegEx. E.g. ensure the (f) or capturing group surround with a specific format /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.

I just want to highlight the two parameters of function makes finding a specific format and replacing a capturing group within the format possible.

SOLUTION

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

for replace all grup occurrences use /(f)/g regexp. The problem in your code: String.prototype.toUpperCase.apply("$1") and "$1".toUpperCase() gives "$1" (try in console by yourself) - so it not change anything and in fact you call twice a.replace( /(f)/, "$1") (which also change nothing).

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

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

Given a dictionary (object, in this case, a Map) of property, values, and using .bind() as described at 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);

Using a JavaScript plain object and with a function defined to get return matched property value of the object, or original string if no match is found

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);
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!