core-js 中 String.prototype.matchAll 的实现解析:从提案到标准 Polyfill 📅 发布时间:2026/9/12 2:21:55 👁 浏览次数: core-js 中 String.prototype.matchAll 的实现解析从提案到标准 Polyfill【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js导读String.prototype.matchAll是 ECMAScript 标准为字符串提供的全局正则匹配迭代接口它解决传统String#match无法获取捕获组与匹配位置、以及循环调用RegExp#exec状态易错的痛点。本文以 String matchAll 提案文档 为骨架结合 core-js 仓库中的真实实现源码与单元测试深入讲解其 API 签名、Entry Point 用法、底层迭代器与Symbol.matchAll机制帮助你理解并正确使用这一标准 API。提案与标准状态String#matchAll最初由 TC39 提案 proposal-string-matchall 提出随后被收入 ECMAScript 2020 标准。在 core-js 中它被归类为stable ES 特性而非仍在草案阶段的 esnext 特性因此除了core-js/proposals/string-match-all这个 proposal 入口之外core-js/es、core-js/stable、core-js/actual、core-js/full等标准入口同样会包含该方法的 polyfill。从仓库的模块组织也可以印证这一点该方法的核心实现位于 modules/es.string.match-all.js模块命名采用es.前缀代表 stable ECMAScript 特性而与之配套的 well-known symbol 实现位于 modules/es.symbol.match-all.js。API 签名根据关联文档该提案定义的内建签名如下class String { matchAll(regexp: RegExp): Iterator; }即String.prototype.matchAll(regexp)接收一个正则表达式返回一个迭代器Iterator。每次调用迭代器的next()都会得到一个包含当前匹配结果及其元信息index、input的数组直到匹配结束返回{ done: true }。需要注意一个关键约束传入的正则表达式必须带gglobal标志。在 core-js 的实现中modules/es.string.match-all.js当传入对象是 RegExp 且 flags 中不包含g时会直接抛出TypeErrorif (isRegExp(regexp)) { flags toString(requireObjectCoercible(getRegExpFlags(regexp))); if (!~stringIndexOf(flags, g)) throw new $TypeError(.matchAll does not allow non-global regexes); }原因在于matchAll内部的迭代逻辑依赖RegExp.prototype.lastIndex随匹配推进见下文迭代器实现非 global 正则不会自动推进lastIndex若不强制g标志将导致无限循环或语义错乱因此规范选择在入口处直接拒绝。Entry Point 用法关联文档给出了该特性在 core-js 中的提案级入口core-js/proposals/string-match-all该入口文件 proposals/string-match-all.js 的内容非常简洁仅加载对应模块use strict; // https://github.com/tc39/proposal-string-matchall require(../modules/esnext.string.match-all);入口点层级体系core-js 提供了多种粒度的入口详见 Entry points 文档String#matchAll在各层级均有对应入口文件可按需选择入口路径作用core-js/proposals/string-match-all仅加载 matchAll 相关模块历史提案入口core-js/es/string/match-all仅 polyfill stable ES 的String#matchAll含必要依赖core-js/es/instance/match-all返回实例方法绑定unbound entry适合函数式调用core-js/es/string/virtual/match-all返回String.prototype.matchAll方法本身不污染全局core-js/es/symbol/match-all仅 polyfillSymbol.matchAllwell-known symbolcore-js/stable/core-js/actual/core-js/full分别引入稳定版、实际版含 stage 3、全量版 polyfill 时自动包含例如 es/string/match-all.js 内部先依次加载es.object.to-string、es.regexp.exec、es.string.match-all三个依赖模块再通过entryUnbind(String, matchAll)导出use strict; require(../../modules/es.object.to-string); require(../../modules/es.regexp.exec); require(../../modules/es.string.match-all); var entryUnbind require(../../internals/entry-unbind); module.exports entryUnbind(String, matchAll);而 es/instance/match-all.js 则用于获取实例方法便于以matchAll(str, regexp)的方式调用module.exports function (it) { var own it.matchAll; return typeof it string || it StringPrototype || (isPrototypeOf(StringPrototype, it) own StringPrototype.matchAll) ? method : own; };典型使用方式全局 polyfill 后直接调用import core-js/es/string/match-all; const iterator aabc.matchAll(/[ac]/g); for (const match of iterator) { console.log(match[0], match.index, match.input); } // a 0 aabc // a 1 aabc // c 3 aabc使用core-js-pure不污染全局命名空间进行函数式调用import matchAll from core-js-pure/es/string/match-all; const iterator matchAll(aabc, /[ac]/g); console.log([...iterator].map(m m[0])); // [a, a, c]底层实现RegExp String Iteratorcore-js 没有直接依赖运行时的原生matchAll而是在 modules/es.string.match-all.js 中通过createIteratorConstructor自建了一个内部迭代器构造器$RegExpStringIterator。它用内部状态记录regexp、string、global、unicode与donevar $RegExpStringIterator createIteratorConstructor(function RegExpStringIterator(regexp, string, $global, fullUnicode) { setInternalState(this, { type: REGEXP_STRING_ITERATOR, regexp: regexp, string: string, global: $global, unicode: fullUnicode, done: false }); }, REGEXP_STRING, function next() { var state getInternalState(this); if (state.done) return createIterResultObject(undefined, true); var R state.regexp; var S state.string; var match regExpExec(R, S); if (match null) { state.done true; return createIterResultObject(undefined, true); } if (state.global) { if (toString(match[0]) ) R.lastIndex advanceStringIndex(S, toLength(R.lastIndex), state.unicode); return createIterResultObject(match, false); } state.done true; return createIterResultObject(match, false); });next()的核心逻辑可概括为若已完成done直接返回{ value: undefined, done: true }调用抽象操作regExpExec(R, S)见 internals/regexp-exec-abstract.js执行一次匹配匹配结果为null表示结束将状态置为完成匹配成功时若带g标志且匹配到空串match[0] 则调用advanceStringIndexinternals/advance-string-index.js手动推进lastIndex以正确处理u/v标志下的码点边界避免空匹配导致的死循环否则返回当前匹配。这种内部状态 手动推进 lastIndex的设计正是matchAll相比反复手动调用RegExp#exec的优势所在迭代状态被封装进迭代器调用方无需关心lastIndex的保存与恢复。顶层$matchAll辅助函数当传入对象不是 RegExp 时实现会回退到内置构造逻辑modules/es.string.match-all.jsvar $matchAll function (string) { var R anObject(this); var S toString(string); var C speciesConstructor(R, RegExp); var flags toString(getRegExpFlags(R)); var matcher, $global, fullUnicode; matcher new C(C RegExp ? R.source : R, flags); $global !!~stringIndexOf(flags, g); fullUnicode !!~stringIndexOf(flags, u) || !!~stringIndexOf(flags, v); matcher.lastIndex toLength(R.lastIndex); return new $RegExpStringIterator(matcher, S, $global, fullUnicode); };这里值得注意的是通过speciesConstructor尊重Symbol.species允许子类化 RegExp 时创建子类实例fullUnicode同时识别u与v标志v是 unicodeSets 标志core-js 一并兼容新构造的matcher.lastIndex会复制原正则的lastIndex保证调用前后外部正则状态不被污染。Symbol.matchAll让自定义对象可被 matchAllString#matchAll的规范定义中若传入的是对象非原始字符串场景或非 RegExp 对象会尝试获取其Symbol.matchAll方法并调用见实现中 modules/es.string.match-all.js 的getMethod(regexp, MATCH_ALL)分支。core-js 同时提供了 well-known symbol 的 polyfilluse strict; var defineWellKnownSymbol require(../internals/well-known-symbol-define); // Symbol.matchAll well-known symbol // https://tc39.es/ecma262/#sec-symbol.matchall defineWellKnownSymbol(matchAll);这意味着你可以为自己的类实现[Symbol.matchAll]方法从而让该类的实例能够直接作为matchAll的参数实现完全自定义的类正则匹配语义import core-js/es/symbol/match-all; import core-js/es/string/match-all; const customMatcher { Symbol.matchAll { return [str.slice(0, 2), str.slice(2)][Symbol.iterator](); }, }; console.log([...hello.matchAll(customMatcher)]); // [he, llo]此外在非 pure 环境下如果RegExp.prototype上缺少Symbol.matchAll实现还会把内部$matchAll直接挂载上去MATCH_ALL in RegExpPrototype || defineBuiltIn(RegExpPrototype, MATCH_ALL, $matchAll)保证regexpSymbol.matchAll这一调用形式可用。兼容性守卫检测原生实现的非 global 限制实现顶部有一段值得关注的能力检测逻辑modules/es.string.match-all.jsvar WORKS_WITH_NON_GLOBAL_REGEX !!nativeMatchAll !fails(function () { nativeMatchAll(a, /./); });它先取原生的.matchAll再探测原生实现是否允许非 global 正则若原生实现符合规范对非 global 正则抛错WORKS_WITH_NON_GLOBAL_REGEX为false此时String.prototype.matchAll保持原生实现forced: false若原生实现存在偏差例如某些旧引擎允许非 global 正则直接匹配一次core-js 会强制覆盖为符合规范的 polyfill 实现。在 polyfill 内部只要原生实现规范就会优先走nativeMatchAll(O, regexp)WORKS_WITH_NON_GLOBAL_REGEX分支把性能敏感的执行路径交给引擎原生能力仅保留边界语义由 polyfill 兜底——这是 core-js 一贯的原生优先、polyfill 兜底策略的典型体现。单元测试佐证仓库的 pure 版单测 tests/unit-pure/es.string.match-all.js 直接使用core-js-pure/es/string/match-all入口验证了上述行为import matchAll from core-js-pure/es/string/match-all; QUnit.test(String#matchAll, assert { assert.isFunction(matchAll); let data [aabc, { toString() { return aabc; } }]; for (const target of data) { const iterator matchAll(target, /[ac]/g); assert.isIterator(iterator); assert.isIterable(iterator); assert.deepEqual(iterator.next(), { value: assign([a], { input: aabc, index: 0 }), done: false, }); // ... } });测试要点包括matchAll本身是可调用的函数且对原始字符串与带toString的对象装箱场景行为一致返回对象既是Iterator又是Iterable可被for...of与展开运算符消费每次next()返回的数组带index与input扩展属性与规范要求一致通过STRICT常量控制的严格模式用例还会断言非 global 正则抛错等边界行为。常见问题与最佳实践1. 为什么必须用g标志因为迭代推进依赖lastIndex的自动更新规范强制要求g。若确实只想匹配一次请改用String#match或RegExp#exec。2. 空匹配会不会死循环不会。core-js 在匹配到空串时会用advanceStringIndex结合u/v标志处理码点推进lastIndex这与规范行为一致。例如/x*/g匹配空串时会逐码点前进。3. 与match的区别String#match返回数组g时只含匹配文本无捕获组信息而matchAll返回迭代器每个元素都包含完整的匹配数组含捕获组、index、input适合需要遍历所有匹配并提取分组信息的场景。4. 如何按需引入避免打包体积膨胀优先使用最细粒度的入口如core-js/es/string/match-all仅 stable ES 所需需要函数式调用且不污染全局时选择core-js-pure/es/string/match-all。总结String.prototype.matchAll从 TC39 提案演进为 ECMAScript 2020 标准 API 的过程中core-js 为其提供了完整的 polyfill 支持既有符合规范的自研RegExpStringIterator迭代器也有Symbol.matchAllwell-known symbol 支持还有面向非 global 正则原生实现的强制修正。通过 proposals/string-match-all.js 或各标准层级入口即可按需引入配合 单元测试 可验证其在任意目标环境中的行为一致性。【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考