es-toolkit/compat 的 repeat 函数:Lodash 兼容的字符串重复实现与源码解析
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
本篇技术指南聚焦 es-toolkit 兼容层(es-toolkit/compat)中的repeat函数,讲解其在字符串重复场景下的调用方式、参数行为、边界处理,并结合 src/compat/string/repeat.ts 的源码与 src/compat/string/repeat.spec.ts 的测试用例,深入剖析其内部实现原理与适用边界,帮助你在从 Lodash 迁移或日常开发中正确选用这一工具。
一、函数概览与使用前提
repeat(str, n?)用于将字符串重复指定次数后返回新字符串,其行为与 Lodash 的_.repeat保持 1:1 兼容,可从es-toolkit/compat导入:
import { repeat } from 'es-toolkit/compat'; const repeated = repeat(str, n);::: warning 官方建议优先使用原生 API
es-toolkit/compat的repeat因为需要处理非字符串值转换与整数转换等 Lodash 兼容逻辑,运行速度比 JavaScript 原生方法更慢。若你不需要 Lodash 兼容行为,官方文档明确建议直接使用更快的原生方法String.prototype.repeat:
'abc'.repeat(2); // 'abcabc':::
该函数的适用场景是:当你的代码库正从 Lodash 迁移到 es-toolkit,且希望保持调用点不变、行为完全一致时。根据 docs/compat/intro.md 的迁移指引,推荐路径是先把lodash/lodash-es的导入替换为es-toolkit/compat,待清理完调用点后再切换到更精简的es-toolkit严格 API。repeat这类带隐式类型转换与多参数形态的函数正是 compat 层的典型代表。
二、基础用法
按指定次数重复字符串
import { repeat } from 'es-toolkit/compat'; // 重复 2 次 repeat('abc', 2); // Returns: 'abcabc' // 重复 3 次 repeat('hello', 3); // Returns: 'hellohellohello' // 重复 0 次返回空字符串 repeat('abc', 0); // Returns: ''当重复次数n小于 1 时,函数返回空字符串;若原字符串本身为空字符串,则原样返回。
null/undefined视为空字符串
与 Lodash 行为一致,当第一个参数为null或undefined时,会被当作空字符串处理:
import { repeat } from 'es-toolkit/compat'; repeat(null, 3); // Returns: '' repeat(undefined, 2); // Returns: ''这一行为在源码中由toString工具保证(见下文源码解析)。
省略重复次数时默认重复 1 次
当第二个参数未指定时,repeat默认重复一次,即返回字符串本身:
import { repeat } from 'es-toolkit/compat'; repeat('abc'); // Returns: 'abc'参数说明
| 参数 | 类型 | 说明 |
|---|---|---|
str | string(可选) | 要重复的字符串,null/undefined按空字符串处理 |
n | number(可选) | 重复次数,默认为1;小于 1 时返回空字符串 |
返回值:string,即按指定次数重复后的新字符串。
三、源码实现解析
repeat的完整实现位于 src/compat/string/repeat.ts,核心逻辑如下:
export function repeat(str?: string, n?: number): string; export function repeat(str: any, n?: any, guard?: any): string { if (guard ? isIterateeCall(str, n, guard) : n === undefined) { n = 1; } else { n = toInteger(n); } if (n < 1 || n > MAX_SAFE_INTEGER) { return ''; } return toString(str).repeat(n); }整个实现可拆解为三个关键环节:
1. 默认值与 iteratee 调用形态处理
第一个分支处理两种情况:
- 未传
n:当n === undefined时直接赋值为1,对应“省略重复次数默认重复 1 次”的文档行为。 - 作为 iteratee 使用:当传入第三个参数
guard时,会调用内部工具isIterateeCall(str, n, guard)判断这次调用是否来自map之类的迭代上下文。
isIterateeCall的实现位于 src/compat/_internal/isIterateeCall.ts:
export function isIterateeCall(value: unknown, index: unknown, object: unknown): boolean { if (!isObject(object)) { return false; } if ( (typeof index === 'number' && isArrayLike(object) && isIndex(index) && index < object.length) || (typeof index === 'string' && index in object) ) { return eq((object as any)[index], value); } return false; }也就是说,当repeat被作为回调传给map等数组方法时(例如['a', 'b', 'c'].map(repeat)),map会以(value, index, array)三个参数调用它,guard即为数组本身。此时isIterateeCall会校验value是否等于数组中该索引位置的值,从而避免把index误当成重复次数。这一点在 src/compat/string/repeat.spec.ts 中有对应的测试用例:
it('should be used as a iteratee', () => { const array = ['a', 'b', 'c']; const actual = array.map(repeat); expect(actual).toEqual(['a', 'b', 'c']); });这保证了与 Lodash 相同的 iteratee 语义:['a', 'b', 'c'].map(repeat)得到['a', 'b', 'c']而非重复后的结果。
2. 重复次数的整数化与边界校验
当n为显式传入的数值时,会先经过toInteger转换(见 src/compat/util/toInteger.ts):
export function toInteger(value: any): number { const finite = toFinite(value); const remainder = finite % 1; return remainder ? finite - remainder : finite; }toInteger先把任意值转换为有限数字,再向下取整去除小数部分。因此:
repeat('abc', 0.5)中0.5被转换为0,返回空字符串;- 字符串形式的数字(如
'3')也会被正确转换。
转换完成后进行边界校验:
if (n < 1 || n > MAX_SAFE_INTEGER) { return ''; }其中MAX_SAFE_INTEGER直接取Number.MAX_SAFE_INTEGER(见 src/compat/_internal/MAX_SAFE_INTEGER.ts)。这意味着:
n < 1(包括0、负数、转换后为0的小数)返回空字符串;n > Number.MAX_SAFE_INTEGER(包括Infinity)同样返回空字符串,避免产生不可控的超大字符串。
测试用例对这两类边界均有覆盖(src/compat/string/repeat.spec.ts):
it('should return empty string when n is less than 1', () => { expect(repeat('abc', 0)).toBe(''); expect(repeat('abc', -1)).toBe(''); expect(repeat('abc', -5)).toBe(''); expect(repeat('abc', 0.5)).toBe(''); }); it('should return empty string when n is greater than MAX_SAFE_INTEGER', () => { const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; expect(repeat('abc', MAX_SAFE_INTEGER + 1)).toBe(''); expect(repeat('abc', Infinity)).toBe(''); });3. 字符串转换后调用原生repeat
最终执行依靠toString(str).repeat(n),其中toString来自 src/compat/util/toString.ts:
export function toString(value: any): string { if (value == null) { return ''; } return baseToString(value); }toString的关键行为包括:
null/undefined返回'',这正是“null或undefined视为空字符串”的底层来源;- 数组会被递归拼接为逗号分隔的字符串(稀疏数组的洞按
undefined渲染); Symbol调用其toString();- 保留
-0的符号(返回'-0'); - 其他值通过字符串拼接(
value + '')完成转换,遵循默认 hint 的valueOf()优先读取顺序。
由此可见,compat 版本的repeat之所以比原生方法慢,正是因为它在前置环节承担了类型转换、iteratee 识别和边界校验这三层额外工作,最终才把结果委托给原生String.prototype.repeat。
四、导出入口与按需引入
repeat在兼容层的主入口 src/compat/compat.ts 中导出:
export { repeat } from './string/repeat.ts';你可以通过以下方式引入:
// 从整体 compat 入口导入 import { repeat } from 'es-toolkit/compat'; // 按函数单独导入(无打包器 / CommonJS 环境下更友好) const repeat = require('es-toolkit/compat/repeat');按 docs/compat/intro.md 的说明,es-toolkit/compat的每个函数都提供独立入口,只加载该函数依赖的文件,适合在无 tree-shaking 的环境(如 CommonJSrequire()、React Native、直接运行于 Node.js)下减小体积。
五、实践建议与边界总结
- 新代码优先用原生 API:官方文档明确警告 compat 版
repeat因兼容逻辑而更慢,非迁移场景直接使用String.prototype.repeat即可。 - 迁移期放心使用:若你的代码库正从 Lodash 迁移,
es-toolkit/compat的repeat与 Lodash 行为 1:1 兼容(自 v1.39.3 起通过 Lodash 自身测试套件),调用点无需改写。 - 牢记边界行为:
n小于 1、大于Number.MAX_SAFE_INTEGER或为Infinity时返回空字符串;null/undefined视为空串;省略n默认重复 1 次;作为map等方法的 iteratee 时不会被误判次数。 - 参考实现与测试:完整的实现与验证可继续阅读 src/compat/string/repeat.ts 及 src/compat/string/repeat.spec.ts,相关工具函数见 src/compat/util/toInteger.ts、src/compat/util/toString.ts 与 src/compat/_internal/isIterateeCall.ts。
【免费下载链接】es-toolkitA modern JavaScript utility library that's 2-3 times faster and up to 97% smaller, a major upgrade to lodash.项目地址: https://gitcode.com/GitHub_Trending/es/es-toolkit
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考