ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

core-js 中的 String.prototype.codePoints 提案实现:按码点遍历字符串并追踪 UTF-16 位置

core-js 中的 String.prototype.codePoints 提案实现:按码点遍历字符串并追踪 UTF-16 位置 core-js 中的 String.prototype.codePoints 提案实现按码点遍历字符串并追踪 UTF-16 位置【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js本篇文章聚焦 core-js 仓库中对 TC39 Stage 1 提案String.prototype.codePoints的完整实现讲解该方法的 API 形态、迭代器语义、UTF-16 代理对surrogate pair处理原理以及它在 core-js 中的各个入口点entry points与测试验证。读完本文你将掌握如何在任意 ES5 环境下使用codePoints()按 Unicode 码点逐个遍历字符串同时拿到每个码点在原字符串中的 UTF-16 起始位置并能从源码层面理解它与String.prototype[Symbol.iterator]及codePointAt的异同。一、提案背景与文档定位String.prototype.codePoints是 TC39 的 Stage 1 提案规范见 proposal-string-prototype-codepoints提案仓库为 tc39/proposal-string-prototype-codepoints其目标是为字符串提供一种“既能按码点遍历、又能报告码点位置”的迭代能力。core-js 将其收录为esnext系列即尚未进入正式 ECMAScript 标准、按提案进度分级的模块之一文档位于 docs/web/docs/features/proposals/string-prototype-codepoints.md实现模块为 packages/core-js/modules/esnext.string.code-points.js。由于该功能仍属提案阶段core-js 只在其proposals与full级别的入口暴露它而不会出现在stable标准已定稿入口中。二、API 形态与迭代器语义文档给出的类型签名如下class String { codePoints(): Iterator{ codePoint, position }; }codePoints()不接受任何参数调用后返回一个迭代器对象迭代器每次next()产出的value是一个包含两个字段的对象codePoint当前字符对应的 Unicode 码点数值number例如字母q为113position该码点第一个 UTF-16 代码单元在原始字符串中的下标从 0 开始。文档中的示例for (let { codePoint, position } of qwe.codePoints()) { console.log(codePoint); // 113, 119, 101 console.log(position); // 0, 1, 2 }由于返回的是标准迭代器它天然支持for...of、解构、Array.from、展开运算符[...str.codePoints()]等所有迭代协议消费方式。与既有迭代器的区别这里需要区分两个概念String.prototype[Symbol.iterator]for...of字符串按码点迭代但只给出码点本身会被当作一个整体无法得知该码点在字符串中的位置String.prototype.codePointAt(i)按下标访问能算出某个下标处码点的值但下标仍以 UTF-16 代码单元为单位——U20BB7这类补充平面字符占 2 个代码单元用codePointAt(1)拿到的其实是代理对的后半部分。codePoints()则同时补齐了两者的短板它既按码点迭代不会被代理对拆开又通过position字段给出每个码点在 UTF-16 字符串中的真实起始下标。这在需要“按视觉字符遍历、同时做精确下标换算”的场景如文本编辑器光标定位、分词、索引重建非常有用。三、core-js 源码实现剖析核心实现位于 packages/core-js/modules/esnext.string.code-points.js整个模块只有约 40 行依赖若干 internals 模块。其实现思路可拆成四步。1. 方法挂载$({ target: String, proto: true, forced: true }, ...)$({ target: String, proto: true, forced: true }, { codePoints: function codePoints() { return new $StringIterator(toString(requireObjectCoercible(this))); } });proto: true表示挂载到String.prototype上forced: true表示无论宿主环境是否已有该方法都强制覆盖为 core-js 的实现保证语义一致调用时先用requireObjectCoercible对this做对象强制转换检查null/undefined会抛TypeError再经toString规整为字符串然后构造内部迭代器$StringIterator并返回。2. 内部迭代器基于createIteratorConstructorvar $StringIterator createIteratorConstructor(function StringIterator(string) { setInternalState(this, { type: STRING_ITERATOR, string: string, index: 0 }); }, String, function next() { var state getInternalState(this); var string state.string; var index state.index; var point; if (index string.length) return createIterResultObject(undefined, true); point charAt(string, index); state.index point.length; return createIterResultObject({ codePoint: codeAt(point, 0), position: index }, false); });关键点迭代器内部状态保存在InternalStateModulepackages/core-js/internals/internal-state.js中包含string与indexnext()判断index string.length时返回{ value: undefined, done: true }结束迭代否则调用多字节工具charAt(string, index)取出当前码点的完整字符然后state.index point.length前进point.length个代码单元对代理对是 2对 BMP 字符是 1最后产出{ codePoint, position: index }迭代器名称createIteratorConstructor的第二个参数为String因此其Symbol.toStringTag为String Iterator与原生String.prototype[Symbol.iterator]返回的迭代器一致源码第 16 行还留有TODO: unify with String#iterator的注释提示未来可能与String#iterator统一。3. 代理对处理string-multibyte工具charAt与codeAt来自 packages/core-js/internals/string-multibyte.js该文件通过闭包工厂createMethod生成两个方法codeAtcreateMethod(false)返回下标处码点数值对应规范中的String.prototype.codePointAtcharAtcreateMethod(true)返回下标处完整字符字符串BMP 字符返回 1 个代码单元代理对返回 2 个代码单元的切片。其核心是代理对判定逻辑first charCodeAt(S, position); return first 0xD800 || first 0xDBFF || position 1 size || (second charCodeAt(S, position 1)) 0xDC00 || second 0xDFFF ? /* 单代码单元分支 */ : /* 组合分支(first - 0xD800 10) (second - 0xDC00) 0x10000 */;即只有当前单元落在高代理区0xD800–0xDBFF、且紧跟一个落在低代理区0xDC00–0xDFFF的单元时才按公式(first - 0xD800 10) (second - 0xDC00) 0x10000组合出补充平面码点否则按单代码单元处理。在codePoints()迭代器中charAt负责“按码点步进”codeAt(point, 0)负责“把取出的字符字符串还原为码点数值”二者配合即实现了“代理对不拆开、位置精确记录”的语义。4. 提案入口与 stage 挂载packages/core-js/proposals/string-code-points.js 仅一行负责引入实现模块packages/core-js/stage/1.js 第 18 行通过require(../proposals/string-code-points)将其纳入 Stage 1 提案集合——因此当你import core-js/stage/1或引入core-js/proposals/string-code-points时该方法即被加载。四、代理对下的位置语义position的进阶验证在 BMP 字符组成的字符串中position与码点序号恰好一一对应qwe为 0、1、2。但一旦混入补充平面字符position代表的是 UTF-16 代码单元下标而非码点序号。这一点在 tests/unit-global/esnext.string.code-points.js 与 tests/unit-pure/esnext.string.code-points.js 中有明确验证iterator .codePoints(); // next() - { value: { codePoint: 134071, position: 0 }, done: false } // next() - { value: { codePoint: 134071, position: 2 }, done: false } // next() - { value: { codePoint: 134071, position: 4 }, done: false } // next() - { value: undefined, done: true }U20BB7的码点为134071它由两个 UTF-16 代码单元组成因此三个的起始position依次为 0、2、4。也就是说你可以同时得到「码点数量 3」与「UTF-16 长度 6」两类信息并据此做两种单位间的精确换算。此外测试还断言String.prototype.codePoints是函数arity形参个数为 0name为codePoints且不可枚举nonEnumerable返回的迭代器满足isIterator与isIterable其Symbol.toStringTag为String IteratorString(iterator)输出[object String Iterator]在Symbol()上下文调用会抛错throws on symbol context对应requireObjectCoercible/toString对 Symbol 的拒绝。五、core-js 各入口点的使用方式文档给出的两个入口点分别对应不同的导入粒度在 core-js 仓库中入口文件位于packages/core-js下发布到 npm 后即为core-js包路径。1. 提案级入口一次引入全部相关提案模块core-js/proposals/string-code-points对应仓库文件 packages/core-js/proposals/string-code-points.js。适合希望按提案粒度统一引入的场景import core-js/proposals/string-code-points; for (const { codePoint, position } of ab.codePoints()) { console.log(codePoint, position); // 97 0 / 134071 1 / 98 3 }2. full 级入口按方法精确加载core-js(-pure)/full/string/code-points对应仓库文件packages/core-js/full/string/code-points.js导出unbound形态即codePoints(qwe)这种把字符串作为参数传入的调用方式通过entry-unbind(String, codePoints)生成适用于函数式编程packages/core-js/full/string/virtual/code-points.js通过getBuiltInPrototypeMethod导出prototype形态qwe.codePoints()packages/core-js/full/instance/code-points.js针对core-js-pure下“实例方法必须先取方法再调用”的约定提供instance形态的包装。对应的core-js-pure用法纯函数、不污染全局见 tests/unit-pure/esnext.string.code-points.jsimport codePoints from core-js-pure/full/string/code-points; const iterator codePoints(qwe); iterator.next(); // { value: { codePoint: 113, position: 0 }, done: false }3. 完整示例结合Array.from收集全部码点import core-js/full/string/code-points; const entries Array.from(a.codePoints()); // [ { codePoint: 134071, position: 0 }, { codePoint: 97, position: 2 } ]六、兼容性前提与使用边界该方法属于TC39 Stage 1 提案语义仍在演进中正式规范落地前不建议在长期稳定的核心业务中依赖其行为细节core-js 中它仅通过proposals与full入口暴露stable入口不包含它stage/1.js中挂载、proposals/中引入路径参见 packages/core-js/stage/1.js使用for...of消费迭代器时需确保宿主环境支持迭代协议与Symbol.iteratorES2015在更老的环境可改用iterator.next()手动驱动返回的迭代器与原生字符串迭代器同为String Iterator标签但 core-js 在 packages/core-js/modules/esnext.string.code-points.js 中留有TODO: unify with String#iterator的注释说明未来版本可能把两者的内部实现统一迭代器对象的内部构造细节不应被业务代码依赖。七、小结String.prototype.codePoints是 core-js 提案实现中“语义小巧、实现精巧”的代表作之一它以约 40 行的模块代码借助string-multibyte的代理对判定提供了「按码点迭代 UTF-16 位置追踪」的组合能力。通过本文的源码与测试交叉验证你可以放心地在需要精确处理 emoji、生僻字等补充平面字符的场景中使用codePoints()并清楚地知道每个码点的position究竟代表什么含义。参考路径速查提案文档docs/web/docs/features/proposals/string-prototype-codepoints.md实现模块packages/core-js/modules/esnext.string.code-points.js多字节工具packages/core-js/internals/string-multibyte.js提案入口packages/core-js/proposals/string-code-points.jsStage 1 挂载packages/core-js/stage/1.jsfull 级入口packages/core-js/full/string/code-points.js、packages/core-js/full/string/virtual/code-points.js、packages/core-js/full/instance/code-points.js全局模式测试tests/unit-global/esnext.string.code-points.jspure 模式测试tests/unit-pure/esnext.string.code-points.js使用文档Entry points 说明docs/web/docs/usage.md【免费下载链接】core-jsStandard Library项目地址: https://gitcode.com/GitHub_Trending/co/core-js创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表