es-toolkit/compat 的 repeat 函数:Lodash 兼容的字符串重复实现与源码解析
2026/9/16 6:25:36 网站建设 项目流程

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/compatrepeat因为需要处理非字符串值转换与整数转换等 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 行为一致,当第一个参数为nullundefined时,会被当作空字符串处理:

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'

参数说明

参数类型说明
strstring(可选)要重复的字符串,null/undefined按空字符串处理
nnumber(可选)重复次数,默认为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返回'',这正是“nullundefined视为空字符串”的底层来源;
  • 数组会被递归拼接为逗号分隔的字符串(稀疏数组的洞按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/compatrepeat与 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询