diff --git a/files/zh-cn/web/javascript/reference/global_objects/string/replace/index.md b/files/zh-cn/web/javascript/reference/global_objects/string/replace/index.md index d257bc8e4eeb16..6a641992c3cdc4 100644 --- a/files/zh-cn/web/javascript/reference/global_objects/string/replace/index.md +++ b/files/zh-cn/web/javascript/reference/global_objects/string/replace/index.md @@ -5,126 +5,165 @@ slug: Web/JavaScript/Reference/Global_Objects/String/replace {{JSRef}} -**`replace()`** 方法返回一个由替换值(`replacement`)替换部分或所有的模式(`pattern`)匹配项后的新字符串。模式可以是一个字符串或者一个[正则表达式](/zh-CN/docs/Web/JavaScript/Reference/RegExp),替换值可以是一个字符串或者一个每次匹配都要调用的回调函数。**如果`pattern`是字符串,则仅替换第一个匹配项。** - -原字符串不会改变。 +**`replace()`** 方法返回一个新字符串,其中一个、多个或所有匹配的 `pattern` 被替换为 `replacement`。`pattern` 可以是字符串或 {{jsxref("RegExp")}},`replacement` 可以是字符串或一个在每次匹配时调用的函数。如果 `pattern` 是字符串,则只会替换第一个匹配项。原始的字符串不会改变。 {{EmbedInteractiveExample("pages/js/string-replace.html")}} ## 语法 -```plain -str.replace(regexp|substr, newSubStr|function) +```js-nolint +replace(pattern, replacement) ``` ### 参数 -- `regexp` (pattern) - - : 一个{{jsxref("RegExp")}} 对象或者其字面量。该正则所匹配的内容会被第二个参数的返回值替换掉。 -- `substr` (pattern) - - : 一个将被 `newSubStr` 替换的 {{jsxref("String","字符串")}}。其被视为一整个字符串,而不是一个正则表达式。仅第一个匹配项会被替换。 -- `newSubStr` (replacement) - - : 用于替换掉第一个参数在原字符串中的匹配部分的{{jsxref("String", "字符串")}}。该字符串中可以内插一些特殊的变量名。参考下面的[使用字符串作为参数](#使用字符串作为参数)。 -- `function` (replacement) - - : 一个用来创建新子字符串的函数,该函数的返回值将替换掉第一个参数匹配到的结果。参考下面的[指定一个函数作为参数](#指定一个函数作为参数)。 +- `pattern` + - : 可以是字符串或者一个带有 [`Symbol.replace`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/replace) 方法的对象,典型的例子就是[正则表达式](/zh-CN/docs/Web/JavaScript/Guide/Regular_expressions)。任何没有 `Symbol.replace` 方法的值都会被强制转换为字符串。 +- `replacement` + - : 可以是字符串或函数。 + - 如果是字符串,它将替换由 `pattern` 匹配的子字符串。支持一些特殊的替换模式,请参阅下面的[指定字符串作为替换项](#指定字符串作为替换项)部分。 + - 如果是函数,将为每个匹配调用该函数,并将其返回值用作替换文本。下面的[指定函数作为替换项](#指定函数作为替换项)部分描述了提供给此函数的参数。 ### 返回值 -一个部分或全部匹配由替代模式所取代的新的字符串。 +一个新的字符串,其中一个、多个或所有的匹配项都被指定的替换项替换。 ## 描述 -该方法并不改变调用它的字符串本身,而只是返回一个新的替换后的字符串。 +该方法并不改变调用它的字符串本身,而是返回一个新的字符串。 + +字符串模式只会被替换一次。要执行全局搜索和替换,请使用带有 `g` 标志的正则表达式或使用 [`replaceAll()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replaceAll)。 + +如果 `pattern` 是一个带有 [`Symbol.replace`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/replace) 方法的对象(包括 `RegExp` 对象),则该方法将被调用,传入目标字符串和 `replacement` 作为参数。它的返回值成为 `replace()` 的返回值。在这种情况下,`replace()` 的行为完全由 `@@replace` 方法定义——例如,下面的说明中提到的任何"捕获组"都实际上是由 [`RegExp.prototype[@@replace]`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@replace) 提供的功能。 + +如果 `pattern` 是一个空字符串,则替换项将被插入到字符串的开头。 + +```js +"xxx".replace("", "_"); // "_xxx" +``` + +`replace()` 替换多次的唯一情况是传入带有 `g` 标志的正则表达式。有关正则表达式属性(特别是 [sticky](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky) 标志)如何与 `replace()` 交互的更多信息,请参阅 [`RegExp.prototype[@@replace]()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@replace)。 + +### 指定字符串作为替换项 + +替换字符串可以包括以下特殊替换模式: -在进行全局的搜索替换时,正则表达式需包含 `g` 标志。 +| 模式 | 插入值 | +| -------- | ------------------------------------------------------------------- | +| `$$` | 插入一个 `"$"`。 | +| `$&` | 插入匹配的子字符串。 | +| `` $` `` | 插入匹配子字符串之前的字符串片段。 | +| `$'` | 插入匹配子字符串之后的字符串片段。 | +| `$n` | 插入第 `n`(索引从 1 开始)个捕获组,其中 `n` 是小于 100 的正整数。 | +| `$` | 插入名称为 `Name` 的命名捕获组。 | -### 使用字符串作为参数 +只有当 `pattern` 参数是一个 {{jsxref("RegExp")}} 对象时,`$n` 和 `$` 才可用。如果 `pattern` 是字符串,或者相应的捕获组在正则表达式中不存在,则该模式将被替换为一个字面量。如果该组存在但未匹配(因为它是一个分支的一部分),则将用空字符串替换它。 -替换字符串可以插入下面的特殊变量名: +```js +"foo".replace(/(f)/, "$2"); +// "$2oo";正则表达式没有第二个组 + +"foo".replace("f", "$1"); +// "$1oo";pattern 是一个字符串,所以它没有任何组 + +"foo".replace(/(f)|(g)/, "$2"); +// "oo";第二个组存在但未匹配 +``` + +### 指定函数作为替换项 -| 变量名 | 代表的值 | -| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `$$` | 插入一个 "$"。 | -| `$&` | 插入匹配的子串。 | -| `` $` `` | 插入当前匹配的子串左边的内容。 | -| `$'` | 插入当前匹配的子串右边的内容。 | -| `$n` | 假如第一个参数是 {{jsxref("RegExp")}}对象,并且 n 是个小于 100 的非负整数,那么插入第 n 个括号匹配的字符串。提示:索引是从 1 开始。如果不存在第 n 个分组,那么将会把匹配到到内容替换为字面量。比如不存在第 3 个分组,就会用“$3”替换匹配到的内容。 | -| `$` | 这里*`Name`* 是一个分组名称。如果在正则表达式中并不存在分组(或者没有匹配),这个变量将被处理为空字符串。只有在支持命名分组捕获的浏览器中才能使用。 | +你可以将第二个参数指定为函数。在这种情况下,匹配完成后将调用该函数。函数的结果(返回值)将用作替换字符串。 -### 指定一个函数作为参数 +> **备注:** 上述特殊替换模式*不*适用于替换器函数返回的字符串。 -你可以指定一个函数作为第二个参数。在这种情况下,当匹配执行后,该函数就会执行。函数的返回值作为替换字符串。 (注意:上面提到的特殊替换参数在这里不能被使用。) 另外要注意的是,如果第一个参数是正则表达式,并且其为全局匹配模式,那么这个方法将被多次调用,每次匹配都会被调用。 +该函数具有以下签名: -下面是该函数的参数: +```js +function replacer(match, p1, p2, /* …, */ pN, offset, string, groups) { + return replacement; +} +``` -| 变量名 | 代表的值 | -| ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `match` | 匹配的子串。(对应于上述的$&。) | -| `p1,p2, ...` | 假如 replace() 方法的第一个参数是一个{{jsxref("RegExp")}} 对象,则代表第 n 个括号匹配的字符串。(对应于上述的$1,$2 等。)例如,如果是用 `/(\a+)(\b+)/` 这个来匹配,`p1` 就是匹配的 `\a+`,`p2` 就是匹配的 `\b+`。 | -| `offset` | 匹配到的子字符串在原字符串中的偏移量。(比如,如果原字符串是 `'abcd'`,匹配到的子字符串是 `'bc'`,那么这个参数将会是 1) | -| `string` | 被匹配的原字符串。 | -| NamedCaptureGroup | 命名捕获组匹配的对象 | +该函数的参数如下所示: -(精确的参数个数依赖于 `replace()` 的第一个参数是否是一个正则表达式({{jsxref("RegExp")}})对象,以及这个正则表达式中指定了多少个括号子串,如果这个正则表达式里使用了命名捕获,还会添加一个命名捕获的对象) +- `match` + - : 匹配的子字符串。(对应于上面的 `$&`。) +- `p1, p2, …, pN` + - : 如果 `replace()` 的第一个参数是 {{jsxref("RegExp")}} 对象,则为捕获组(包括命名捕获组)找到的第 `n` 个字符串。(对应于上面的 `$1`、`$2` 等。)例如,如果 `pattern` 是 `/(\d+)(\w+)/`,则 `p1` 是 `\a+` 的匹配项,`p2` 是 `\b+` 的匹配项。如果该组是分支的一部分(例如 `"abc".replace(/(a)|(b)/, Replacer)`),则不匹配的替代项将为 `undefined`。 +- `offset` + - : 原始字符串中匹配子字符串的偏移量。例如,如果整个字符串是 `'abcd'`,而匹配的子字符串是 `'bc'`,那么这个参数将是 `1`。 +- `string` + - : 正在检查的原始字符串。 +- `groups` + - : 一个捕获组命名组成的对象,值是匹配的部分(如果没有匹配,则为 `undefined`)。仅在 `pattern` 包含至少一个命名捕获组时才存在。 -下面的例子将会使 `newString` 变成 `'abc - 12345 - #$*%'`: +参数的确切数量取决于第一个参数是否为 {{jsxref("RegExp")}} 对象,以及它有多少个捕获组。 + +以下示例将设置 `newString` 为 `'abc - 12345 - #$*%'`: ```js function replacer(match, p1, p2, p3, offset, string) { - // p1 is nondigits, p2 digits, and p3 non-alphanumerics + // p1 是非数字,p2 是数字,且 p3 非字母数字 return [p1, p2, p3].join(" - "); } -var newString = "abc12345#$*%".replace(/([^\d]*)(\d*)([^\w]*)/, replacer); +const newString = "abc12345#$*%".replace(/([^\d]*)(\d*)([^\w]*)/, replacer); console.log(newString); // abc - 12345 - #$*% ``` +如果第一个参数中的正则表达式是全局的,那么为了替换每个完全匹配的字符串,该函数将被多次调用。 + ## 示例 -### 在 `replace()` 中使用正则表达式 +### 在 replace() 中定义正则表达式 -在下面的例子中,`replace()` 中使用了正则表达式及忽略大小写标示。 +在以下示例中,正则表达式在 `replace()` 中定义,并包括忽略大小写标志。 ```js -var str = "Twas the night before Xmas..."; -var newstr = str.replace(/xmas/i, "Christmas"); +const str = "Twas the night before Xmas..."; +const newstr = str.replace(/xmas/i, "Christmas"); console.log(newstr); // Twas the night before Christmas... ``` -### 在 `replace()` 中使用 `global` 和 `ignore` 选项 +这将打印 `'Twas the night before Christmas...'`。 -下面的例子中,正则表达式包含有全局替换 (g) 和忽略大小写 (i) 的选项,这使得 replace 方法用'oranges'替换掉了所有出现的"apples". +> **备注:** 有关正则表达式的更多解释,请参阅[正则表达式指南](/zh-CN/docs/Web/JavaScript/Guide/Regular_expressions)。 -```js -var re = /apples/gi; -var str = "Apples are round, and apples are juicy."; -var newstr = str.replace(re, "oranges"); +### 在 replace() 中使用 global 和 ignoreCase 标志 + +只能使用正则表达式进行全局替换。在以下示例中,正则表达式包括 [global 和 ignoreCase 标志](/zh-CN/docs/Web/JavaScript/Guide/Regular_expressions#通过标志进行高级搜索),允许 `replace()` 将字符串中每个出现的 `'apples'` 替换为 `'oranges'`。 -// oranges are round, and oranges are juicy. -console.log(newstr); +```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 代替替换文本。 +以下脚本交换字符串中的单词。对于替换文本,脚本使用[捕获组](/zh-CN/docs/Web/JavaScript/Guide/Regular_expressions/Groups_and_backreferences)以及 `$1` 和 `$2` 替换模式。 ```js -var re = /(\w+)\s(\w+)/; -var str = "John Smith"; -var newstr = str.replace(re, "$2, $1"); -// Smith, John -console.log(newstr); +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) { - return "-" + match.toLowerCase(); + function upperToHyphenLower(match, offset, string) { + return (offset > 0 ? "-" : "") + match.toLowerCase(); } return propertyName.replace(/[A-Z]/g, upperToHyphenLower); } @@ -132,80 +171,68 @@ function styleHyphenFormat(propertyName) { 运行 `styleHyphenFormat('borderTop')`,将返回 `'border-top'`。 -因为我们想在最终的替换中进一步转变匹配结果,所以我们必须使用一个函数。这迫使我们在使用{{jsxref("String.prototype.toLowerCase()", "toLowerCase()")}}方法前进行评估。如果我们尝试不用一个函数进行匹配,那么使用{{jsxref("String.prototype.toLowerCase()", "toLowerCase()")}} 方法将不会有效。 +由于我们希望在最终替换之前进一步转变匹配*结果*,所以我们必须使用一个函数。这会强制在使用 {{jsxref("String.prototype.toLowerCase()", "toLowerCase()")}} 方法之前对匹配进行求值。如果我们尝试在没有函数的情况下使用匹配来完成这个操作,{{jsxref("String.prototype.toLowerCase()", "toLowerCase()")}} 方法将没有效果。 -```js -var newString = propertyName.replace(/[A-Z]/g, "-" + "$&".toLowerCase()); // won't work +```js example-bad +// 不会产生作用 +const newString = propertyName.replace(/[A-Z]/g, "-" + "$&".toLowerCase()); ``` -这是因为 `'$&'.toLowerCase()` 会先被解析成字符串字面量(这会导致相同的'$&') 而不是当作一个模式。 +这是因为 `'$&'.toLowerCase()` 会首先作为一个字符串字面量进行求值(结果仍然是 `'$&'`),然后再将其作为匹配模式使用。 -### 将华氏温度转换为对等的摄氏温度 +### 将华氏温度转换为相应的摄氏温度 -下面的例子演示如何将华氏温度转换为对等的摄氏温度。华氏温度用一个数字加一个"F"来表示,这个函数将返回一个数字加"C"来表示的摄氏温度。例如,如果输入是 212F,这个函数将返回 100C。如果输入的数字是 0F,这个方法将返回 "-17.77777777777778C"。 +下面的例子演示如何将华氏温度转换为相应的摄氏温度。华氏温度用一个数字加一个 `"F"` 来表示,这个函数将返回一个数字加 `"C"` 来表示的摄氏温度。例如,如果输入是 `"212F"`,这个函数将返回 `"100C"`。如果输入的数字是 `"0F"`,这个方法将返回 `"-17.77777777777778C"`。 -正则表达式 test 检查任何数字是否以 F 结尾。华氏温度通过第二个参数 p1 进入函数。这个函数基于华氏温度作为字符串传递给 f2c 函数设置成摄氏温度。然后 f2c() 返回摄氏温度。这个函数与 Perl 的 s///e 标志相似。 +正则表达式 `test` 用于检查以 `F` 结尾的任何数字。华氏度的数值通过函数的第二个参数 `p1` 传入函数。该函数根据传递给 `f2c()` 函数的字符串中的华氏度数值设置摄氏度数值,然后 `f2c()` 函数返回摄氏度数值。该函数实现了类似 Perl 的 `s///e` 标志的功能。 ```js function f2c(x) { function convert(str, p1, offset, s) { - return ((p1 - 32) * 5) / 9 + "C"; + return `${((p1 - 32) * 5) / 9}C`; } - var s = String(x); - var test = /(\d+(?:\.\d*)?)F\b/g; + const s = String(x); + const test = /(-?\d+(?:\.\d*)?)F\b/g; return s.replace(test, convert); } ``` -### 使用行内函数和正则来避免循环 - -下例把某种模式的字符串转换为一个对象数组(其元素为对象)。 - -**输入:** -一个由 x,- 和 \_ 组成的字符串。 - -```plain -x-x_ - ----x---x---x--- +### 创建一个通用的替换器 --xxx-xx-x- +假设我们想创建一个替换器,将偏移数据附加到每个匹配的字符串中。因为替换函数已经接收到 `offset` 参数,如果正则表达式是静态已知的,那么这将变得很简单。 -_x_x___x___x___ +```js +"abcd".replace(/(bc)/, (match, p1, offset) => `${match} (${offset}) `); +// "abc (1) d" ``` -**输出:** +然而,如果我们希望这个替换器能够适用于任何正则表达式模式,那么它将很难泛化。替换器是*可变*参数的,它接收的参数数量取决于存在的捕获组数量。我们可以使用[剩余参数参数](/zh-CN/docs/Web/JavaScript/Reference/Functions/rest_parameters),但它也会将 `offset`、`string` 等收集到数组中。根据正则表达式的特性,`groups` 可能会被传递或者不会被传递,这也使得很难泛化地知道哪个参数对应于 `offset`。 -一个数组对象。'x' 产生一个 'on' 状态,'-'(连接符)产生一个 'off' 状态,而 '\_'(下划线)表示 'on' 状态的长度。 +```js example-bad +function addOffset(match, ...args) { + const offset = args.at(-2); + return `${match} (${offset}) `; +} -```js -[ - { on: true, length: 1 }, - { on: false, length: 1 }, - { on: true, length: 2 } - ... -] +console.log("abcd".replace(/(bc)/, addOffset)); // "abc (1) d" +console.log("abcd".replace(/(?bc)/, addOffset)); // "abc (abcd) d" ``` -代码片段: +在上面的 `addOffset` 示例中,当正则表达式包含一个命名组时,它无法正常工作,因为在这种情况下 `args.at(-2)` 是 `string` 而不是 `offset`。 + +相反,你需要根据类型提取最后几个参数,因为 `groups` 是一个对象,而 `string` 是一个字符串。 ```js -var str = "x-x_"; -var retArr = []; -str.replace(/(x_*)|(-)/g, function (match, p1, p2) { - if (p1) { - retArr.push({ on: true, length: p1.length }); - } - if (p2) { - retArr.push({ on: false, length: 1 }); - } -}); +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(retArr); +console.log("abcd".replace(/(bc)/, addOffset)); // "abc (1) d" +console.log("abcd".replace(/(?bc)/, addOffset)); // "abc (1) d" ``` -该代码片段生成了一个数组,包含三个期望格式的对象,避免了使用 for 循环语句。 - ## 规范 {{Specifications}} @@ -214,16 +241,12 @@ console.log(retArr); {{Compat}} -### Firefox 备注 - -- `flags` 是一个仅在 Gecko 中可用的非标准的第三方参数:`str.replace(regexp|substr, newSubStr|function, flags)` -- 从 Gecko 27 开始,这个方法就被整合到了 ECMAScript 规范中。当 replace() 被使用全局 g 标志的正则表达式调用时,{{jsxref("RegExp.lastIndex")}} 属性将被重置为 0([Firefox bug 501739](https://bugzil.la/501739))。 -- 从 Gecko 39 开始,`flags` 参数便被弃用,且在调用时会抛出一个控制台警告([Firefox bug 1142351](https://bugzil.la/1142351))。 -- 从 Gecko 47 开始,在非发行版本中已不再支持非标准的 `flags` 参数,并且不久后会完全移除该参数([Firefox bug 1245801](https://bugzil.la/1245801))。 -- 从 Gecko 49 开始,不再支持非标准的 `flags` 参数([Firefox bug 1108382](https://bugzil.la/1108382))。 - ## 参见 -- {{jsxref("String.prototype.match()")}} -- {{jsxref("RegExp.prototype.exec()")}} -- {{jsxref("RegExp.prototype.test()")}} +- [`core-js` 中 `String.prototype.replace` 的 Polyfill,修复了一些问题,并实现了现代行为,比如 `Symbol.replace` 的支持](https://github.com/zloirock/core-js#ecmascript-string-and-regexp) +- {{jsxref("String.prototype.replaceAll", "String.prototype.replaceAll()")}} +- {{jsxref("String.prototype.match", "String.prototype.match()")}} +- {{jsxref("RegExp.prototype.exec", "RegExp.prototype.exec()")}} +- {{jsxref("RegExp.prototype.test", "RegExp.prototype.test()")}} +- [`Symbol.replace`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Symbol/replace) +- [`RegExp.prototype[@@replace]()`](/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp/@@replace)