ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JavaScript 正则方法全指南:String 与 RegExp 的 match、matchAll、replace、exec、test 深度解析

JavaScript 正则方法全指南:String 与 RegExp 的 match、matchAll、replace、exec、test 深度解析 文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载本文对应 Modern JavaScript Tutorialen.javascript.info 仓库正则表达式章节的收官之作——9-regular-expressions/17-regexp-methods/article.md。正则表达式本身只是模式真正让它发挥威力的是与字符串、RegExp 对象绑定的那一组方法。读完本文你将完整掌握str.match、str.matchAll、str.split、str.search、str.replace、str.replaceAll、regexp.exec、regexp.test八类方法的全部模式、返回结构与隐蔽陷阱并能根据取全部匹配、取捕获组、定位替换、精确位置扫描等不同诉求正确选型。方法总览先看一张速查表建立全局印象方法调用者核心用途无匹配时的返回str.match(regexp)字符串查找匹配首条或全部取决于g标志nullstr.matchAll(regexp)字符串查找全部匹配且每条都带捕获组空的可迭代对象str.split(regexp\|substr, limit)字符串按分隔符拆分字符串至少含一个元素的数组str.search(regexp)字符串返回首个匹配的位置-1str.replace(regexp\|str, str\|func)字符串搜索并替换首条或全部原字符串str.replaceAll(str\|regexp, str\|func)字符串替换字符串/正则的所有出现原字符串regexp.exec(str)正则对象可手动控制搜索位置的逐条匹配nullregexp.test(str)正则对象判断是否存在匹配false注意一个关键分工match、matchAll、split、search、replace、replaceAll是字符串的方法由字符串调用、传入正则而exec、test是RegExp 对象的方法由正则调用、传入字符串。后者受正则自身的lastIndex属性影响详见下文这是两类方法行为差异的根本来源。str.match(regexp)基础匹配三种模式str.match(regexp)在字符串str中查找regexp的匹配。它有三种模式行为差异完全由正则是否带g标志决定。模式一无g标志——返回首条匹配 捕获组如果没有g标志返回第一条匹配形式是数组result[0]为完整匹配result[1]、result[2]……依次为各捕获组内容并附带index匹配位置与input源字符串两个属性let str I love JavaScript; let result str.match(/Java(Script)/); alert( result[0] ); // JavaScript (完整匹配) alert( result[1] ); // Script (第一个捕获组) alert( result.length ); // 2 // 附加信息: alert( result.index ); // 7 (匹配位置) alert( result.input ); // I love JavaScript (源字符串)捕获组括号(...)的编号从左到右按左括号排列编号规则、嵌套分组与可选分组的细节参见仓库中的 9-regular-expressions/11-regexp-groups/article.md。模式二有g标志——返回全部匹配的字符串数组带g标志时返回所有匹配组成的数组但每条匹配只是普通字符串不再包含捕获组内容也没有index、input属性let str I love JavaScript; let result str.match(/Java(Script)/g); alert( result[0] ); // JavaScript alert( result.length ); // 1这是很多初学者踩坑的地方想用g拿到所有匹配结果却丢失了括号里的捕获组。若需要全部匹配 每个匹配的捕获组请使用下面的str.matchAll。模式三无匹配——返回null而非空数组无论有没有g标志只要没有匹配match返回的都是null而不是空数组。这是一个极易被遗忘的细节let str I love JavaScript; let result str.match(/HTML/); alert(result); // null alert(result.length); // Error: Cannot read property length of null如果希望结果永远是数组可以在后面接一个空数组兜底let result str.match(regexp) || [];这样后续result.length、result[0]等操作就不会因为null而崩溃。str.matchAll(regexp)带捕获组的全局匹配str.matchAll(regexp)是str.match的新版改进专门用于同时获取全部匹配与每个匹配的捕获组。它与match有三点差异返回的是可迭代对象iterable而不是数组可以用Array.from转成真正的数组每个匹配都以数组形式返回格式与无g标志的str.match完全一致含捕获组、index、input没有匹配时返回空的可迭代对象而不是null。let str h1Hello, world!/h1; let regexp /(.*?)/g; let matchAll str.matchAll(regexp); alert(matchAll); // [object RegExp String Iterator], 不是数组, 而是可迭代对象 matchAll Array.from(matchAll); // 转成数组 let firstMatch matchAll[0]; alert( firstMatch[0] ); // h1 (完整匹配) alert( firstMatch[1] ); // h1 (捕获组) alert( firstMatch.index ); // 0 (匹配位置) alert( firstMatch.input ); // h1Hello, world!/h1 (源字符串)如果直接用for..of遍历就不需要Array.fromfor (let match of str.matchAll(regexp)) { // 依次处理每个匹配及其捕获组 }为什么返回的是可迭代对象而不是数组从设计上讲这是出于优化考虑调用matchAll时并不会真正执行搜索而是返回一个惰性的迭代器真正匹配在每次迭代时才发生。例如文本中潜在有 100 处匹配但for..of循环里找到 5 处就break了引擎就不会再花时间找剩下的 95 处。matchAll要求传入的正则必须带g标志否则会抛错且每条匹配的格式与无g的match结果一致因此它天然与捕获组配合良好详见 9-regular-expressions/11-regexp-groups/article.md 中用 matchAll 搜索带组的所有匹配一节。str.split(regexp|substr, limit)按正则拆分split以正则或子字符串作为分隔符拆分字符串返回数组。用字符串拆分是最常见用法alert(12-34-56.split(-)) // 数组 [12, 34, 56]传正则的写法完全一样此时分隔符本身是一个模式——例如按逗号加任意数量空白拆分alert(12, 34, 56.split(/,\s*/)) // 数组 [12, 34, 56]split的第二参数limit可限制返回数组的最大长度超出部分被丢弃。需要注意的是split的正则不带g也没关系因为它天然就是全局拆分这一点与match、replace的g语义不同。str.search(regexp)只找首个匹配的位置str.search(regexp)返回第一条匹配的起始位置找不到返回-1let str A drop of ink may make a million think; alert( str.search( /ink/i ) ); // 10 (首个匹配位置)重要限制search只返回首个匹配的位置无法拿到后续匹配。如果需要所有匹配的位置应改用str.matchAll(regexp)后逐个读取每条匹配的index属性。str.replace(str|regexp, str|func)搜索替换的瑞士军刀replace是正则方法中最有用的一个常被称为搜索替换的瑞士军刀。它的第一个参数可以是字符串或正则第二个参数可以是替换字符串或回调函数组合出四种用法。用字符串做替换只替换首个匹配第一个参数传字符串时只替换第一处匹配// 把第一个破折号换成冒号 alert(12-34-56.replace(-, :)) // 12:34-56可以看到只有第一个-被替换成了:。要替换全部破折号必须改用正则/-/g且必须带g标志// 替换所有破折号为冒号 alert( 12-34-56.replace(/-/g, :) ) // 12:34-56替换字符串中的特殊符号第二个参数是替换字符串时可以嵌入以下特殊符号来引用匹配的各个部分符号在替换字符串中的作用$插入整个匹配$插入匹配位置之前的字符串$插入匹配位置之后的字符串$nn为 1~2 位数字插入第 n 个捕获组的内容参见 9-regular-expressions/11-regexp-groups/article.md$name插入指定名称的命名捕获组内容同上$$插入字符$本身利用$n与$name可以非常优雅地重组文本。最经典的例子是交换姓名的顺序let str John Smith; // 交换名与姓 alert(str.replace(/(john) (smith)/i, $2, $1)) // Smith, John配合命名捕获组(?name...)和$name日期格式的转换year-month-day→day.month.year也同样一目了然完整示例见 9-regular-expressions/11-regexp-groups/article.md 的替换中的捕获组一节。用函数做替换终极替换能力当替换逻辑需要智能处理时第二个参数可以传函数。该函数会对每一处匹配调用一次其返回值被插入为替换文本。函数签名如下func(match, p1, p2, ..., pn, offset, input, groups)各参数含义match—— 本次匹配的完整文本p1, p2, ..., pn—— 各捕获组的内容若存在offset—— 匹配在源字符串中的位置input—— 源字符串groups—— 命名捕获组组成的对象。如果正则中没有括号则函数只收到 3 个参数func(str, offset, input)。把所有匹配转成大写let str html and css; let result str.replace(/html|css/gi, str str.toUpperCase()); alert(result); // HTML and CSS用匹配位置本身替换匹配/ho/gi在Ho-Ho-ho中的位置分别是 0、3、6alert(Ho-Ho-ho.replace(/ho/gi, (match, offset) offset)); // 0-3-6正则中有两个括号时回调收到 5 个参数完整匹配、两个捕获组以及本例未使用的匹配位置和源字符串let str John Smith; let result str.replace(/(\w) (\w)/, (match, name, surname) ${surname}, ${name}); alert(result); // Smith, John捕获组很多时用 rest 参数接收更省事let str John Smith; let result str.replace(/(\w) (\w)/, (...match) ${match[2]}, ${match[1]}); alert(result); // Smith, John若使用了命名捕获组groups对象永远是最后一个参数可以pop()出来let str John Smith; let result str.replace(/(?name\w) (?surname\w)/, (...match) { let groups match.pop(); return ${groups.surname}, ${groups.name}; }); alert(result); // Smith, John函数式替换的威力在于它拿到匹配的全部信息、能访问外部变量、能执行任意逻辑——本质上是把正则匹配 程序化处理完整地串在了一起。str.replaceAll(str|regexp, str|func)一次性替换所有出现replaceAll与replace基本一致但有两点关键差异第一个参数传字符串时替换的是所有出现而replace只替换首个出现第一个参数传正则时正则必须带g标志否则直接抛错带g时行为与replace相同。它的主战场正是替换字符串的所有出现// 替换所有破折号为冒号 alert(12-34-56.replaceAll(-, :)) // 12:34-56需要替换的是普通字符串而非复杂模式时replaceAll是比replace(/-/g, ...)更直白的选择。regexp.exec(str)可精确控制位置的逐条匹配与前文所有方法不同exec是RegExp 对象的方法调用方式是regexp.exec(str)。它的行为取决于正则是否带g标志。无g标志等价于 str.match不带g时regexp.exec(str)返回第一条匹配格式与str.match(regexp)完全一致。这种情况下exec没有带来任何新能力。带g标志结合 lastIndex 逐条推进带g标志时exec表现出状态机式的行为首次调用regexp.exec(str)返回第一条匹配并把regexp.lastIndex保存为匹配结束之后的位置下一次调用从regexp.lastIndex位置开始继续搜索返回下一条匹配并再次更新lastIndex依此类推直到没有匹配为止没有匹配时返回null并把regexp.lastIndex重置为0。也就是说重复调用exec会借助regexp.lastIndex一条一条地返回全部匹配。在str.matchAll出现之前这是获取全部匹配 捕获组的标准做法let str More about JavaScript at https://javascript.info; let regexp /javascript/ig; let result; while (result regexp.exec(str)) { alert( Found ${result[0]} at position ${result.index} ); // 先输出 Found JavaScript at position 11 // 再输出 Found javascript at position 33 }这个循环写法至今依然有效只是新浏览器中str.matchAll通常更方便。两种方式的取舍也值得留意exec循环提供了对匹配过程的手动控制权详见 9-regular-expressions/16-regexp-sticky/article.md 中对regexp.exec与matchAll的对比讨论。手动设置 lastIndex从指定位置开始搜索利用lastIndex可以人为指定搜索起点let str Hello, world!; let regexp /\w/g; // 不带 g 时 lastIndex 属性会被忽略 regexp.lastIndex 5; // 从第 5 个位置逗号处开始搜索 alert( regexp.exec(str) ); // world配合 y 标志精确位置匹配带g标志时exec是从lastIndex开始向后找最近的匹配而带ysticky标志时搜索被限定在lastIndex这一确切位置再往后哪怕有匹配也不会命中let str Hello, world!; let regexp /\w/y; regexp.lastIndex 5; // 只在位置 5 精确搜索 alert( regexp.exec(str) ); // null (位置 5 是逗号不是单词)这在从字符串的某个精确位置读取内容的场景如词法分析中极其重要y标志还能避免g模式一路扫到文本末尾的性能开销。完整原理与实战见 9-regular-expressions/16-regexp-sticky/article.md。regexp.test(str)判断是否存在匹配regexp.test(str)只关心有没有匹配有则返回true无则返回falselet str I love JavaScript; // 下面两种写法等价 alert( /love/i.test(str) ); // true alert( str.search(/love/i) ! -1 ); // true反例let str Bla-bla-bla; alert( /love/i.test(str) ); // false alert( str.search(/love/i) ! -1 ); // false全局正则与 lastIndex 的联动与exec一样正则带g标志时test也会从regexp.lastIndex开始搜索并更新该属性。因此它同样可以配合lastIndex从指定位置开始测试let regexp /love/gi; let str I love JavaScript; // 从位置 10 开始搜索: regexp.lastIndex 10; alert( regexp.test(str) ); // false (10 之后没有匹配)陷阱警告同一个全局正则在多个字符串上复用会失败test会推进lastIndex导致同一个全局正则被反复用于不同输入时结果错误——因为第二次搜索可能从非零位置开始let regexp /javascript/g; // 刚创建: regexp.lastIndex 0 alert( regexp.test(javascript) ); // true (此时 regexp.lastIndex 变为 10) alert( regexp.test(javascript) ); // false第二次调用返回false正是因为lastIndex已经不再是0。两种规避办法每次搜索前手动regexp.lastIndex 0干脆改用字符串方法str.match、str.search等——字符串方法不使用lastIndex天然免疫这个问题。exec同样受此规则约束在循环、复用正则时务必留意lastIndex的累积效应。总结按场景选择方法只需首条匹配含捕获组str.match(regexp)无g需要全部匹配但不要捕获组str.match(regexp)带g需要全部匹配 每个匹配的捕获组str.matchAll(regexp)正则须带g只想知道匹配位置str.search(regexp)仅首个位置按模式拆分字符串str.split(regexp)替换字符串版全部出现str.replaceAll(str, ...)替换正则版全部出现str.replace(regexp, ...)或str.replaceAll(regexp, ...)正则须带g替换复杂逻辑 / 需要访问捕获组与上下文str.replace(regexp, func)需要手动控制搜索位置、逐条推进regexp.exec(str)lastIndex配合y标志可做到精确位置匹配只判断是否存在匹配regexp.test(str)。值得反复强调的是两条贯穿全文的规则第一g标志决定一条还是全部match、replace如此exec/test的lastIndex行为也依赖它第二无匹配时match返回null、exec返回null、matchAll返回空迭代器——针对不同的返回形态做好空值处理是写出健壮正则代码的前提。本文内容出自仓库 9-regular-expressions/17-regexp-methods/article.md同一章节体系下的前置知识正则模式与标志、捕获组、sticky 标志 y、前瞻与后顾同样位于仓库9-regular-expressions/目录可与本文对照阅读形成完整的正则方法知识闭环。赞分享文档/教程前端【免费下载链接】en.javascript.infoModern JavaScript Tutorial项目地址https://gitcode.com/gh_mirrors/en/en.javascript.info点击查看免费下载相关推荐CANN/hccl PyTorch AllReduce示例Performing AllReduce Operations Using PyTorch Sample Description This sample demLint代码质量JavaScript 正则表达式方法详解RegExp 和 String 的协作JavaScript 正则表达式方法详解RegExp 和 String 的协作 正则表达式是 JavaScript 中强大的文本处理工具而掌握相关方法的使用文档/教程前端千古前端图文教程JavaScript 正则表达式入门与实战RegExp 对象、匹配模式与 String 正则方法全解千古前端图文教程JavaScript 正则表达式入门与实战RegExp 对象、匹配模式与 String 正则方法全解 正则表达式是 JavaScript文档教程前端上一篇Sunshine游戏串流服务器终极指南如何将你的PC变成全平台游戏中心下一篇TypeChat 音乐示例本地 SQLite 数据库迁移指南tracks / albums / playlists / artists 四表结构全解析创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表