Wednesday, May 18, 2022

JavaScript’s Amazingly Versatile Replace Function

Finding and replacing text is one of the oldest and most common tasks in programming, yet, it can still trip up novice and experienced coders alike. As a web developer, you are probably already familiar with JavaScript’s replace() function. I thought I was too, until I had to use it recently. I soon found out that there is more to the replace() function than I was aware of. Although it only takes two arguments, this simple signature belies replace’s amazing versatility, as these can be of different types, depending on how exacting you need it to be. This JavaScript programming tutorial will provide an overview of JS’s Swiss army knife of functions and examples of its many uses.

JavaScript replace(): One Function, Three Modes of Operation

Technically speaking, replace() is a public method of the String object. As such, the invoking object is the string to be searched, while the two arguments comprise the substring and replacement substring respectively:

let newStr = str.replace(substr, newSubstr);

The replace() method returns a new string with a substring (substr) replaced by a new one (newSubstr), while leaving the invoking string unchanged. Here is an example:

let source = "Can you hear the rumble? Can you hear the rumble that's calling?";
let newStr = source.replace("rumble", "thunder");
console.log(newStr);
//outputs: "Can you hear the thunder? Can you hear the rumble that's calling?" 

Notice that only the first instance of “rumble” was changed. Replace performs a substitution on the first match it finds. At least, that is how it behaves in its most basic form. It can also accept a regular expression (RegEx) search substring for more sophisticated matching as well as a replacer function so that the real signature looks more like this:

let newStr = str.replace(substr: String | RegEx, newSubstr: String | Function);

So, without further ado, let’s give these variations a try!

Read: Date Parsing Using JavaScript and Regular Expressions

Matching by RegEx in JavaScript

Regular expressions, or RegExes for short, are a dedicated pattern matching library dating all the way back to 1951. Today, they are implemented by many modern popular programming languages, including JavaScript. In the case of replace(), RegExes not only greatly enhances its search capabilities, but also allows us to capture substrings within matched content to examine – or even re-insert into – the matched substring in a different position. To illustrate, let’s imagine that we wanted to replace import statements in a file with some other code. These would have a format such as this:

@import 'filename.ext';

In all cases, the “import”, period (.), and terminating semi-colon (;) will be present. However, the filename and extension may differ.

Although there could be several variations, we could use the following RegEx to find the import statements:

/^@import\s+["'](.+)["'];/gm

In case your RegEx syntax is rusty, here is a breakdown of the above statement:

  • The caret (^) matches the start of the line.
  • The “@import” and semi-colon (;) at the start and end of the pattern are literal text to match.
  • The \s+ matches one or more spaces.
  • The [“‘] matches either single or double quotes.
  • The dot (.) Matches any character except line breaks and is equivalent to [^\n\r].
  • The g modifier matches globally so that all occurrences are located.
  • The m modifier tells the Regex that the text contains multiple lines.

We can verify that it works using an online RegEx tester such as regex101:

JavaScript Regex Tutorial

Read: Working with Regular Expressions in Your HTML5 Forms

Capturing Groups with replace() in JavaScript

Notice that the file names are highlighted using a different color. These indicate a captured group. True to their name, captured groups collect multiple tokens together and create a capture group for extracting a substring. Here, the filename is captured so that we could fetch the contents of the file ourselves for replacement purposes.

We could also swap in a different file name by capturing the text before and after the file name. Captured groups in the replacement string may be referenced using the numbered dollar sign ($) variables, where $0 represents the entire matched string and each captured group gets its own number starting at 1:

let newFileContents = fileContents
  .replace(/^(@import\s+["'])(.+)(["'];)/gm,  "$1newfile.css$3");

We can see the resulting string in regex101:

JavaScript replace() Tutorial

Supplying a Replacer Function with replace() in JavaScript

As mentioned earlier, instead of passing a newSubstr as the second parameter of the replace() method, you can pass a replacement function. By doing so, the replace() method will invoke the replacer() function for every match it encounters. It then uses the result of this function as the replacement string.

The replacer() function will receive the following arguments:

function replacer(match, p1, p2, ..., offset, string);

Here is an explanation of each parameter:

  • match: is the matched substring.
  • p1, p2, …pn are the nth string found by a parenthesized capture group provided by the regular expression.
  • offset: is the offset of the matched substring within the whole string being searched.
  • string: is the whole string being examined.

Alluding to the scenario envisioned earlier, we could employ a replacer function to manually insert the contents of the target file within a CSS rule like the following:

.my-component-class {
    @import 'filename.ext';
}

We just need to modify our RegEx to include the opening and closing curly braces (and whitespace). Then, using the above replacer signature as a guide, we can load the contents of the target file and reinsert it between the braces:

let newFileContents = readFile(rawString)
  .replace(/(\{\s*)@import\s+["'](.*)["'];(\s*\})/gm, 
    (match, p1, p2, p3) => p1 + loadLessFile(path.join(commonFilePath, p2)) + p3);

The result is a syntactically correct (albeit non-indented) nested rule:

.my-component-class {
.standard-button {
  background-color: #FFFFFF;
  color: blue;
  * {
      color: #000000;
  }
  border-radius: 0.25rem;
  border: 0.0625rem solid gray;
}
.standard-button:hover:enabled {
  background-color: fade(#000000, 4%);
  color: #000000;
  * {
      color: #000000;
  }
}

.primary-button:disabled,
.standard-button:disabled {
    opacity: 0.38;
    cursor: auto;
}
}

We could go through the trouble of indenting each line of the imported text, but, since LESS files are compiled into regular CSS, the lack of indentation does not pose any problem whatsoever.

Read: HTML, CSS, and JavaScript Tools and Libraries

Final Thoughts on JavaScript replace() Function

When approaching a search and replace task, I like to start with the simplest form of the function and only introduce Regexes and/or the replacer() function if necessary. The decision to employ Regexes is an easy one to make because it’s solely based on whether or not the search string is fixed or varied. A replacer function is usually warranted when you are not performing a straight substitution on matched substrings.

Read more JavaScript programming and web development tutorials.

Rob Gravelle
Rob Gravelle
Rob Gravelle resides in Ottawa, Canada, and has been an IT guru for over 20 years. In that time, Rob has built systems for intelligence-related organizations such as Canada Border Services and various commercial businesses. In his spare time, Rob has become an accomplished music artist with several CDs and digital releases to his credit.

Popular Articles

Featured