String.prototype.replace()

Baseline Widely available

This feature is well established and works across many devices and browser versions. It’s been available across browsers since July 2015.

replace() 方法返回一个新字符串,其中一个、多个或所有匹配的 pattern 被替换为 replacementpattern 可以是字符串或 RegExpreplacement 可以是字符串或一个在每次匹配时调用的函数。如果 pattern 是字符串,则只会替换第一个匹配项。原始的字符串不会改变。

尝试一下

语法

js
replace(pattern, replacement)

参数

pattern

可以是字符串或者一个带有 Symbol.replace 方法的对象,典型的例子就是正则表达式。任何没有 Symbol.replace 方法的值都会被强制转换为字符串。

replacement

可以是字符串或函数。

  • 如果是字符串,它将替换由 pattern 匹配的子字符串。支持一些特殊的替换模式,请参阅下面的指定字符串作为替换项部分。
  • 如果是函数,将为每个匹配调用该函数,并将其返回值用作替换文本。下面的指定函数作为替换项部分描述了提供给此函数的参数。

返回值

一个新的字符串,其中一个、多个或所有的匹配项都被指定的替换项替换。

描述

该方法并不改变调用它的字符串本身,而是返回一个新的字符串。

字符串模式只会被替换一次。要执行全局搜索和替换,请使用带有 g 标志的正则表达式或使用 replaceAll()

如果 pattern 是一个带有 Symbol.replace 方法的对象(包括 RegExp 对象),则该方法将被调用,传入目标字符串和 replacement 作为参数。它的返回值成为 replace() 的返回值。在这种情况下,replace() 的行为完全由 [Symbol.replace]() 方法定义——例如,下面的说明中提到的任何"捕获组"都实际上是由 RegExp.prototype[Symbol.replace] 提供的功能。

如果 pattern 是一个空字符串,则替换项将被插入到字符串的开头。

js
"xxx".replace("", "_"); // "_xxx"

replace() 替换多次的唯一情况是传入带有 g 标志的正则表达式。有关正则表达式属性(特别是 sticky 标志)如何与 replace() 交互的更多信息,请参阅 RegExp.prototype[Symbol.replace]()

指定字符串作为替换项

替换字符串可以包括以下特殊替换模式:

模式 插入值
$$ 插入一个 "$"
$& 插入匹配的子字符串。
$` 插入匹配子字符串之前的字符串片段。
$' 插入匹配子字符串之后的字符串片段。
$n 插入第 n(索引从 1 开始)个捕获组,其中 n 是小于 100 的正整数。
$ 插入名称为 Name 的命名捕获组。

只有当 pattern 参数是一个 RegExp 对象时,$n$<Name> 才可用。如果 pattern 是字符串,或者相应的捕获组在正则表达式中不存在,则该模式将被替换为一个字面量。如果该组存在但未匹配(因为它是一个分支的一部分),则将用空字符串替换它。

js
"foo".replace(/(f)/, "$2");
// "$2oo";正则表达式没有第二个组

"foo".replace("f", "$1");
// "$1oo";pattern 是一个字符串,所以它没有任何组

"foo".replace(/(f)|(g)/, "$2");
// "oo";第二个组存在但未匹配

指定函数作为替换项

你可以将第二个参数指定为函数。在这种情况下,匹配完成后将调用该函数。函数的结果(返回值)将用作替换字符串。

备注:上述特殊替换模式适用于替换器函数返回的字符串。

该函数具有以下签名:

js
function replacer(match, p1, p2, /* …, */ pN, offset, string, groups) {
  return replacement;
}

该函数的参数如下所示:

match

匹配的子字符串。(对应于上面的 $&。)

p1, p2, …, pN

如果 replace() 的第一个参数是 RegExp 对象,则为捕获组(包括命名捕获组)找到的第 n 个字符串。(对应于上面的 $1$2 等。)例如,如果 pattern/(\a+)(\b+)/,则 p1\a+ 的匹配项,p2\b+ 的匹配项。如果该组是分支的一部分(例如 "abc".replace(/(a)|(b)/, Replacer)),则不匹配的替代项将为 undefined

offset

原始字符串中匹配子字符串的偏移量。例如,如果整个字符串是 'abcd',而匹配的子字符串是 'bc',那么这个参数将是 1

string

正在检查的原始字符串。

groups

一个捕获组命名组成的对象,值是匹配的部分(如果没有匹配,则为 undefined)。仅在 pattern 包含至少一个命名捕获组时才存在。

参数的确切数量取决于第一个参数是否为 RegExp 对象,以及它有多少个捕获组。

以下示例将设置 newString'abc - 12345 - #$*%'

js
function replacer(match, p1, p2, p3, offset, string) {
  // p1 是非数字,p2 是数字,且 p3 非字母数字
  return [p1, p2, p3].join(" - ");
}
const newString = "abc12345#$*%".replace(/([^\d]*)(\d*)([^\w]*)/, replacer);
console.log(newString); // abc - 12345 - #$*%

如果第一个参数中的正则表达式是全局的,那么为了替换每个完全匹配的字符串,该函数将被多次调用。

示例

在 replace() 中定义正则表达式

在以下示例中,正则表达式在 replace() 中定义,并包括忽略大小写标志。

js
const str = "Twas the night before Xmas...";
const newstr = str.replace(/xmas/i, "Christmas");
console.log(newstr); // Twas the night before Christmas...

这将打印 'Twas the night before Christmas...'

备注:有关正则表达式的更多解释,请参阅正则表达式指南

在 replace() 中使用 global 和 ignoreCase 标志

只能使用正则表达式进行全局替换。在以下示例中,正则表达式包括 global 和 ignoreCase 标志,允许 replace() 将字符串中每个出现的 'apples' 替换为 'oranges'

js
const re = /apples/gi;
const str = "Apples are round, and apples are juicy.";
const newstr = str.replace(re, "oranges");
console.log(newstr); // oranges are round, and oranges are juicy.

这将打印 'oranges are round, and oranges are juicy'

交换字符串中的两个单词

以下脚本交换字符串中的单词。对于替换文本,脚本使用捕获组以及 $1$2 替换模式。

js
const re = /(\w+)\s(\w+)/;
const str = "Maria Cruz";
const newstr = str.replace(re, "$2, $1");
console.log(newstr); // Cruz, Maria

这将打印 'Cruz, Maria'

使用内联函数来修改匹配到的字符

在这个例子中,字符串中所有出现的大写字母都被转换为小写,并且在匹配位置前插入一个连字符。重要的是,在将匹配项作为替换返回之前,需要对匹配项进行额外的操作。

在返回前,替换函数接受匹配的片段作为参数,并使用它来转换大小写并拼接连字符。

js
function styleHyphenFormat(propertyName) {
  function upperToHyphenLower(match, offset, string) {
    return (offset > 0 ? "-" : "") + match.toLowerCase();
  }
  return propertyName.replace(/[A-Z]/g, upperToHyphenLower);
}

运行 styleHyphenFormat('borderTop'),将返回 'border-top'

由于我们希望在最终替换之前进一步转变匹配结果,所以我们必须使用一个函数。这会强制在使用 toLowerCase() 方法之前对匹配进行求值。如果我们尝试在没有函数的情况下使用匹配来完成这个操作,toLowerCase() 方法将没有效果。

js
// 不会产生作用
const newString = propertyName.replace(/[A-Z]/g, "-" + "$&".toLowerCase());

这是因为 '$&'.toLowerCase() 会首先作为一个字符串字面量进行求值(结果仍然是 '$&'),然后再将其作为匹配模式使用。

将华氏温度转换为相应的摄氏温度

下面的例子演示如何将华氏温度转换为相应的摄氏温度。华氏温度用一个数字加一个 "F" 来表示,这个函数将返回一个数字加 "C" 来表示的摄氏温度。例如,如果输入是 "212F",这个函数将返回 "100C"。如果输入的数字是 "0F",这个方法将返回 "-17.77777777777778C"

正则表达式 test 用于检查以 F 结尾的任何数字。华氏度的数值通过函数的第二个参数 p1 传入函数。该函数根据传递给 f2c() 函数的字符串中的华氏度数值设置摄氏度数值,然后 f2c() 函数返回摄氏度数值。该函数实现了类似 Perl 的 s///e 标志的功能。

js
function f2c(x) {
  function convert(str, p1, offset, s) {
    return `${((p1 - 32) * 5) / 9}C`;
  }
  const s = String(x);
  const test = /(-?\d+(?:\.\d*)?)F\b/g;
  return s.replace(test, convert);
}

创建一个通用的替换器

假设我们想创建一个替换器,将偏移数据附加到每个匹配的字符串中。因为替换函数已经接收到 offset 参数,如果正则表达式是静态已知的,那么这将变得很简单。

js
"abcd".replace(/(bc)/, (match, p1, offset) => `${match} (${offset}) `);
// "abc (1) d"

然而,如果我们希望这个替换器能够适用于任何正则表达式模式,那么它将很难泛化。替换器是可变参数的,它接收的参数数量取决于存在的捕获组数量。我们可以使用剩余参数参数,但它也会将 offsetstring 等收集到数组中。根据正则表达式的特性,groups 可能会被传递或者不会被传递,这也使得很难泛化地知道哪个参数对应于 offset

js
function addOffset(match, ...args) {
  const offset = args.at(-2);
  return `${match} (${offset}) `;
}

console.log("abcd".replace(/(bc)/, addOffset)); // "abc (1) d"
console.log("abcd".replace(/(?<group>bc)/, addOffset)); // "abc (abcd) d"

在上面的 addOffset 示例中,当正则表达式包含一个命名组时,它无法正常工作,因为在这种情况下 args.at(-2)string 而不是 offset

相反,你需要根据类型提取最后几个参数,因为 groups 是一个对象,而 string 是一个字符串。

js
function addOffset(match, ...args) {
  const hasNamedGroups = typeof args.at(-1) === "object";
  const offset = hasNamedGroups ? args.at(-3) : args.at(-2);
  return `${match} (${offset}) `;
}

console.log("abcd".replace(/(bc)/, addOffset)); // "abc (1) d"
console.log("abcd".replace(/(?<group>bc)/, addOffset)); // "abc (1) d"

规范

Specification
ECMAScript Language Specification
# sec-string.prototype.replace

浏览器兼容性

BCD tables only load in the browser

参见