ARTICLE DETAIL

资讯详情

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

es-toolkit compat 层的 truncate 函数:Lodash 兼容的字符串截断实现与源码解析

es-toolkit compat 层的 truncate 函数:Lodash 兼容的字符串截断实现与源码解析 es-toolkit compat 层的 truncate 函数Lodash 兼容的字符串截断实现与源码解析【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit本篇技术指南围绕 es-toolkit 的 Lodash 兼容层es-toolkit/compat中的truncate函数展开完整覆盖其签名、参数、分隔符截断与 Unicode 处理等全部用法并结合仓库中 truncate.ts 的源码与 truncate.spec.ts 测试用例逐行剖析其截断算法、边界行为以及官方文档中关于性能取舍的取舍说明帮助你在从 lodash 迁移时准确理解该函数的行为细节与适用边界。一、为什么需要es-toolkit/compat中的 truncatees-toolkit 是一个现代 JavaScript 工具库其compat子模块的目标是作为 lodash 的drop-in replacement。在 src/compat/index.ts 的模块注释中明确写道es-toolkit/compatwill offer complete compatibility with lodash, ensuring a seamless transition……The primary goal ofes-toolkit/compatis to serve as a drop-in replacement for lodash.也就是说compat 层的函数行为要与 lodash 对齐并通过真实的 lodash 测试用例来验证。truncate就是其中的一个字符串函数它通过 src/compat/compat.ts 中的export { truncate } from ./string/truncate.ts;第 270 行对外导出最终可以从es-toolkit/compat直接导入。官方日文参考文档 docs/ja/compat/reference/string/truncate.md英文版见 docs/compat/reference/string/truncate.md对该函数给出了一个醒目的警告由于复杂的 Unicode 处理与正则表达式检查该truncate函数运行较慢。建议改用更快、更现代的 JavaScriptString.prototype.slice。这一定位非常关键compat 层的truncate是为了行为兼容 lodash而设计的而非为极致性能设计。下面先完整讲解它的用法再回到源码看它慢在哪里。二、函数签名与参数const truncated truncate(str, options);当字符串长度超过指定的最大长度时truncate会将其截断并追加省略字符串默认...。参数定义如下与文档一致参数类型必填默认值说明stringstring否需要截断的字符串optionsobject否—选项对象options.lengthnumber否30字符串最大长度options.omissionstring否...表示文本被省略的字符串options.separatorRegExp \| string否—决定截断位置的分隔符模式返回值string—— 截断后的字符串。对应的源码类型定义见 src/compat/string/truncate.tstype TruncateOptions { length?: number; separator?: string | RegExp; omission?: string; };三、基本用法1. 默认行为最大 30 字符不传options时按默认的length 30截断超出部分被...替换import { truncate } from es-toolkit/compat; // 基本用法最大 30 字符 truncate(hi-diddly-ho there, neighborino); // 返回值: hi-diddly-ho there, neighbo... // 指定长度 truncate(hi-diddly-ho there, neighborino, { length: 24 }); // 返回值: hi-diddly-ho there, n... // 修改省略字符串 truncate(hi-diddly-ho there, neighborino, { omission: [...] }); // 返回值: hi-diddly-ho there, neig [...]注意一个容易忽略的细节截断后的总长度是length包含省略字符串而不是正文 24 字符再加 3 个省略号。当omission: [...]5 个字符时正文只保留30 - 5 25个字符所以结果是hi-diddly-ho there, neig [...]。这一点在源码中由lengthBase Math.max(length - lengthOmission, 0)一行明确体现见下文第五节。2. 使用分隔符在词边界处截断指定separator后函数会在截断点附近寻找分隔符把截断位置回退到最后一个分隔符处从而避免在单词中间断开import { truncate } from es-toolkit/compat; // 以空格作为分隔符在单词边界处截断 truncate(hi-diddly-ho there, neighborino, { length: 24, separator: , }); // 返回值: hi-diddly-ho there,... // 用正则指定分隔符 truncate(hi-diddly-ho there, neighborino, { length: 24, separator: /,? /, }); // 返回值: hi-diddly-ho there...separator: 时截断点落在最后一个空格之后逗号随前面的单词被保留得到hi-diddly-ho there,...separator: /,? /可选逗号 至少一个空格时分隔符本身被吞掉得到更干净的hi-diddly-ho there...。测试用例 truncate.spec.ts 中对此有直接验证it(should support a separator option, () { expect(truncate(string, { length: 24, separator: })).toBe(hi-diddly-ho there,...); expect(truncate(string, { length: 24, separator: /,? / })).toBe(hi-diddly-ho there...); expect(truncate(string, { length: 24, separator: /,? /g })).toBe(hi-diddly-ho there...); });值得注意的是第三个断言带g标志的正则同样被正确处理——源码在拼装内部正则时会主动剥离原有的u标志再重新补上flags.replace(u, )因此传入/…/u或/…/g均不会报错见 truncate.spec.ts 中的对应测试。3. Unicode 字符的正确计数truncate不会把多字节/高码位字符拆坏emoji 等字符按 1 个字符计数import { truncate } from es-toolkit/compat; truncate(¥§✈✉, { length: 5 }); // 返回值: ¥§✈✉ truncate(¥§✈✉, { length: 4, omission: … }); // 返回值: ¥§✈…第一例中¥§✈✉的string.length实际为 7在 UTF-16 中占 2 个码元但按 Unicode 码点计只有 5 个字符因此length: 5时原样返回第二例length: 4时保留前 3 个字符再加单字符省略号…。四、边界行为详解测试用例佐证truncate.spec.ts 覆盖了大量边界场景理解这些行为对正确使用该函数非常重要负数length按0处理length为0或负数时结果直接是省略字符串本身。// length 为 0 或 -2 时 expect(truncate(string, { length: length })).toBe(...);字符串比省略字符串还短若原串长度不超过omission的长度且仍会被截断直接返回omission。例如truncate(ABC, { length: 2 })返回...而不是A...。omission为null/undefined时被字符串化这是刻意对齐 lodash 的行为expect(truncate(string, { omission: null })).toBe(hi-diddly-ho there, neighbnull); expect(truncate(string, { omission: undefined })).toBe(hi-diddly-ho there, nundefined);string被强制转为字符串传入String对象或带toString的对象也能工作null/undefined则视为空字符串truncate(null)返回。分隔符在截断片段中找不到时退回硬截断即在lengthBase处直接切断并追加省略号例如truncate(hello world test, { length: 10, separator: xyz })返回hello w...。可用作 iteratee由于truncate(string?, options?)的第一个参数即为待处理值它可以直接作为map等方法的迭代函数使用const actual map([string, string, string], truncate); // [hi-diddly-ho there, neighbo..., ...]五、源码实现剖析完整实现仅 100 余行见 src/compat/string/truncate.ts。核心算法可以拆成五步1. 参数归一化string string ! null ? ${string} : ; let length 30; let omission ...; if (isObject(options)) { length parseLength(options.length); omission omission in options ? ${options.omission} : ...; }parseLengthtruncate.ts负责把length归一化null用默认值 30 0一律取 0。omission则通过模板字符串强制转字符串这解释了上文中omission: null变成null的怪异但兼容的行为。2. 省略字符串参与长度预算// Unicode length of omission string const lengthOmission Array.from(omission).length; // Unicode length of the string if it is truncated const lengthBase Math.max(length - lengthOmission, 0);Array.from按码点展开计算omission的 Unicode 长度正文预算lengthBase length - lengthOmission。这保证了截断后总长度 ≤ length也解释了第三节中omission: [...]时正文只剩 25 个字符的现象。3. Unicode 检测与双路径处理let strArray: string[] | undefined undefined; const unicode regexMultiByte.test(string); if (unicode) { strArray Array.from(string); i strArray.length; }检测依据是内部正则regexMultiBytesrc/compat/_internal/regexMultiByte.tsexport const regexMultiByte new RegExp( [\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff\\ufe0e\\ufe0f] );该正则匹配零宽连接符、高代理区码点这类增平面的字符会落入\ud800-\udfff范围、组合记号等——命中任意一个即判定为含多字节字符此时改用Array.from(string)按码点展开后再计数与切片。若未命中纯 ASCII 场景则直接走string.slice的快路径避免不必要的数组开销。这正是文档中因复杂 Unicode 处理与正则检查而变慢的主要来源之一。4. 快速返回的两个短路条件if (length i) return string; // 未超长原样返回 if (i lengthOmission) return omission; // 原串比省略号还短直接返回省略号5. 分隔符的二次回退截断let base strArray undefined ? string.slice(0, lengthBase) : strArray?.slice(0, lengthBase).join(); const separator options?.separator; if (!separator) { base omission; return base; } // Further truncate the string to the last separator using unicode regex const search separator instanceof RegExp ? separator.source : separator; const flags u (separator instanceof RegExp ? separator.flags.replace(u, ) : ); const withoutSeparator new RegExp((?result.*(?:(?!${search}).))(?:${search}), flags).exec(base); return (!withoutSeparator?.groups ? base : withoutSeparator.groups.result) omission;这一步是整个算法的精髓先在lengthBase处做一次硬截断得到base再把用户的separator正则取source字符串直接用嵌入到一条带命名捕获组(?result…)的正则中并强制加上uUnicode标志——因此分隔符匹配按码点进行不会拆坏 Unicode 字符该正则是贪婪取base中最后一个分隔符之前的内容。若base里根本没有分隔符groups不存在则回退为硬截断结果这正是边界行为第 5 条的实现。源码文件头部还有一行注释值得留意truncate.ts/** * This regex might more complete detect unicode, but it is slower and this project * desires to mimic the behavior of lodash. */作者明确说明一个更完整的 Unicode 检测正则[^\x00-\x7F]被刻意弃用因为更慢而本项目希望模仿 lodash 的行为。这再次印证了 compat 层truncate的设计优先级行为一致 性能。六、性能提示何时改用String.prototype.slice官方文档的 warning 是选择 API 的直接依据需要与 lodash_.truncate逐位对齐含分隔符回退、负数长度、omission 强制转字符串等怪癖时使用es-toolkit/compat的truncate只是想把字符串截到 N 个字符且输入基本是 BMP 内字符、不需要词边界语义时str.slice(0, n) …更快也不承担正则与 Unicode 分支的开销。从源码结构看truncate的固定开销包括一次regexMultiByte.test全串扫描、命中 Unicode 后的Array.from全串展开、带u标志的正则构造与exec。这些开销在短字符串上不可感知但在对大文本做高频处理时值得考虑原生slice方案。七、相关路径索引内容路径英文文档docs/compat/reference/string/truncate.md日文文档docs/ja/compat/reference/string/truncate.md核心实现src/compat/string/truncate.ts单元测试src/compat/string/truncate.spec.tsUnicode 检测正则src/compat/_internal/regexMultiByte.ts导出位置src/compat/compat.ts 第 270 行【免费下载链接】es-toolkitA modern JavaScript utility library thats 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表