es-toolkit 兼容版 `pad` 函数详解:双向填充字符串的兼容实现与源码剖析
pad 是 es-toolkit 提供的字符串填充工具,用于将字符串在左右两侧补齐指定字符以达到目标长度。本文以 docs/compat/reference/string/pad.md 为主体,深入讲解 es-toolkit/compat 兼容版 pad 的用法、参数规则、与主版本 es-toolkit/string 的差异,以及其底层实现原理(多字节字符处理、null/undefined 容错、字符截断策略),帮助你在迁移 Lodash 或对齐旧代码行为时正确选择和使用该 API。
一、pad 是什么:函数签名与核心语义
pad 在字符串长度不足目标长度时,用指定的填充字符在字符串左右两侧同时补齐。当填充字符无法被均匀分割时,多余的一个字符放在右侧。
const padded = pad(str, length, chars);
完整签名如下(来自 src/compat/string/pad.ts):
export function pad(str?: any, length: any = 0, chars: any = ' '): string;
参数说明
| 参数 | 类型 | 是否必填 | 默认值 | 说明 |
|---|---|---|---|---|
str |
string |
可选 | — | 需要填充的字符串 |
length |
number |
可选 | 0 |
填充后要达到的目标长度 |
chars |
string |
可选 | ' '(空格) |
用于填充的字符 |
返回值
(string):返回填充后达到指定长度的字符串;若无需填充则返回原字符串。
基础示例
import { pad } from 'es-toolkit/compat';
// 使用默认空格填充
pad('abc', 8);
// Returns: ' abc '
// 使用指定字符填充
pad('abc', 8, '_-');
// Returns: '_-abc_-_'
// 已达标时原样返回
pad('abc', 3);
// Returns: 'abc'
// 目标长度更短时原样返回
pad('abc', 2);
// Returns: 'abc'
从上面的例子可以看出两个关键规则:
- 左短右长:
pad('abc', 8)左侧补 2 个空格、右侧补 3 个空格,余数给右侧; - 不足不补:当
length <= str.length时,无论目标长度多短,都直接返回原字符串(测试用例 src/compat/string/pad.spec.ts 明确验证了这一点)。
二、null 与 undefined 的特殊处理
compat 版本与主版本最大的行为差异在于对空值的处理。null 或 undefined 会被当作空字符串处理,这是为了与 Lodash 保持兼容:
import { pad } from 'es-toolkit/compat';
pad(null, 5); // ' '
pad(undefined, 3, '*'); // '***'
测试用例进一步验证了该行为的一致性(见 src/compat/string/pad.spec.ts):
pad(null, 2); // ' '
pad(undefined, 2); // ' '
pad('', 2); // ' '
pad(null, 2, '_-'); // '__'
pad(undefined, 2, '_-'); // '__'
这里 null、undefined 与空字符串 '' 三者行为完全一致——这正是文档中提示“该函数因处理 null 或 undefined 而运行较慢”的原因所在。
三、compat 版与主版本:如何选择
文档开头给出了明确建议:优先使用主版本 es-toolkit/string 的 pad,因为 compat 版为了兼容 Lodash 的容错行为(处理 null/undefined)付出了额外的性能代价。
主版本文档见 docs/reference/string/pad.md,其实现位于 src/string/pad.ts,直接基于原生 API,极为简洁:
export function pad(str: string, length: number, chars = ' '): string {
return str.padStart(Math.floor((length - str.length) / 2) + str.length, chars).padEnd(length, chars);
}
主版本与 compat 版本的主要差异对比:
| 维度 | es-toolkit/string(主版本) |
es-toolkit/compat(兼容版) |
|---|---|---|
| 导入路径 | es-toolkit/string |
es-toolkit/compat |
| 参数类型 | 严格 string |
宽松(任意类型会做隐式转换) |
null/undefined |
不适用(类型层面即拒绝) | 视为空字符串 |
| 实现方式 | 原生 padStart/padEnd |
内部 createPadding + 多字节字符处理 |
| 性能 | 更快 | 稍慢(需容错处理) |
选择建议:
- 新代码:优先使用 es-toolkit/string 的 pad,类型更严格、性能更好;
- 从 Lodash 迁移:当原代码依赖 Lodash 的宽松行为(如传入
null、数字、可转字符串对象)时,使用es-toolkit/compat的pad可做到行为对齐,无需逐处改写。
四、源码级原理剖析:pad 的实现与关键调用链
4.1 主流程:三步骤实现
compat 版 pad 的实现非常紧凑(src/compat/string/pad.ts):
export function pad(str?: any, length: any = 0, chars: any = ' '): string {
const value = toString(str);
const targetLength = toInteger(length);
const strLength = stringSize(value);
if (targetLength <= strLength) {
return value;
}
const mid = (targetLength - strLength) / 2;
const padChars = `${chars}`;
return createPadding(Math.floor(mid), padChars) + value + createPadding(Math.ceil(mid), padChars);
}
整个流程分为四步:
- 类型归一化:
toString(str)将任意输入转为字符串(null→''),toInteger(length)将长度参数取整(负数和 0 都归为 0,字符串'4'会被转为数字4); - 提前返回:
targetLength <= strLength时直接返回原字符串,不做任何填充; - 计算分配:
mid = (targetLength - strLength) / 2,左侧取Math.floor(mid)、右侧取Math.ceil(mid)——这正是“多余字符放右侧”的实现来源; - 拼接结果:左右两侧分别调用
createPadding生成填充串,再与原文拼接。
4.2 核心工具:stringSize 与 createPadding
两侧填充的核心逻辑在 src/compat/_internal/createPadding.ts:
export function stringSize(str: string): number {
return regexMultiByte.test(str) ? Array.from(str).length : str.length;
}
export function createPadding(length: number, chars: string): string {
const charsLength = stringSize(chars);
if (charsLength === 0 || length < 1) {
return '';
}
const result = chars.repeat(Math.ceil(length / charsLength));
return regexMultiByte.test(result) ? Array.from(result).slice(0, length).join('') : result.slice(0, length);
}
这里有三个值得注意的实现细节:
- 多字节字符按码点计数:
stringSize通过regexMultiByte(定义于 src/compat/_internal/regexMultiByte.ts,匹配零宽连接符、星面平面码点、组合变音符等)检测字符串是否含特殊 Unicode 字符;若包含,则用Array.from(str).length按 Unicode 码点计数,而不是用可能切分代理对的str.length; - 循环填充 + 截断:先用
chars.repeat(Math.ceil(length / charsLength))生成足够长的填充串,再精确截取length个字符,避免多字节字符被截断成乱码; - 空字符短路:当
chars长度为 0 或所需长度小于 1 时直接返回空串——测试中pad('abc', 6, '')返回'abc'(见 src/compat/string/pad.spec.ts)正是依赖此分支。
4.3 多字节字符的正确性由测试保障
src/compat/string/pad.spec.ts 中有专门的 emoji 测试用例:
pad('ab', 8, '😀'); // '😀😀😀ab😀😀😀'
pad('😀😁😂', 8, '_'); // '__😀😁😂___'
第一个用例中 3 个 emoji 恰好凑满 6 个填充位,说明 length 按码点数量而非 UTF-16 单元计数;若按 str.length(emoji 占 2 个单元)计算,结果将出现截断残缺。这也是 compat 版不直接使用原生 padStart/padEnd(它们按 UTF-16 单元计数)而要自建 createPadding 的根本原因。
五、兼容行为细节:类型强制与边界情况
5.1 length 会被强制为整数
compat 版通过 toInteger 对 length 做取整与归零处理,测试覆盖了以下边界(src/compat/string/pad.spec.ts):
// 负数按 0 处理
pad('abc', -2); // 'abc'
// 字符串数字会被强制转换
pad('abc', '4'); // 'abc '('4' 被转为 4,补 1 个空格)
pad('abc', ''); // 'abc'('' 被转为 0)
5.2 str 会被强制为字符串
与 Lodash 一致,str 参数接受任意可字符串化的对象(src/compat/string/pad.spec.ts):
pad(Object('abc'), 6); // ' abc '(包装对象)
pad({ toString: () => 'abc' }, 6); // ' abc '(自定义 toString)
5.3 chars 为空串时返回原字符串
当 chars 被强转为空字符串时(如 '' 或空包装对象),函数直接返回原字符串(src/compat/string/pad.spec.ts):
pad('abc', 6, ''); // 'abc'
pad('abc', 6, Object('')); // 'abc'
六、与兄弟函数 padStart、padEnd 的关系
pad 并非孤立存在,compat 目录下还有两个同族函数:
- src/compat/string/padStart.ts:仅在左侧填充,
padStart('abc', 6, '_-')返回'_-_abc'; - src/compat/string/padEnd.ts:仅在右侧填充,
padEnd('abc', 6, '_-')返回'abc_-_'。
三者的实现骨架完全一致(toString → toInteger → stringSize → createPadding),区别仅在于填充位置的组合方式:pad 等价于“左侧 floor(mid) + 右侧 ceil(mid)”的合体。当你只需要单侧对齐(如生成表格列)时,应优先选择 padStart/padEnd 以获得更清晰的语义;需要居中效果(如标题横幅)时才使用 pad。
七、从哪里导入:导出路径速查
pad 在 es-toolkit 中的导出位置如下:
- compat 版本:由 src/compat/compat.ts 统一导出,使用时
import { pad } from 'es-toolkit/compat'; - 主版本:由 src/string/index.ts 导出,使用时
import { pad } from 'es-toolkit/string'。
如果你同时在迁移多个 Lodash 字符串函数,建议对照 docs/compat/reference/string 目录下的其他参考文档(如 padStart、padEnd、trim 等)统一规划导入来源,避免混用两套语义产生行为差异。
总结
es-toolkit/compat的pad(str, length, chars)对字符串两侧进行填充,length不足时原样返回,余数填充在右侧;- 它完整继承了 Lodash 的宽松兼容行为:
null/undefined视为空字符串、参数自动强转、支持多字节字符按码点计数填充; - 底层由
toString、toInteger、stringSize、createPadding组成的调用链实现(见 src/compat/string/pad.ts 与 src/compat/_internal/createPadding.ts),行为正确性由 src/compat/string/pad.spec.ts 的完整测试覆盖; - 新代码建议优先使用更快的 es-toolkit/string 主版本
pad;仅当需要对齐 Lodash 兼容语义时,才选用es-toolkit/compat版本。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust4.25 K640- DDeepSeek-V4.1-FlashDeepSeek-V4.1-Flash 是一个多模态混合专家(MoE)模型,拥有 5520 亿骨干参数,并支持最多一百万 token 的上下文长度。该模型原生支持图像和文本输入,并以自回归方式生成文本Python850
SlideSCIPPT插件,支持素材库、AI助手、一键添加图片标题,复制粘贴位置、一键图片对齐、一键插入Markdown(加粗、超链接等行内样式、代码块、LaTeX等块级样式)、便捷导出图片!C#591
Agent-Reach给你的 AI Agent 一键装上互联网能力。13 个平台(网页/GitHub/YouTube/小红书/B站/Twitter/Reddit 等)多后端路由,当下最稳的接入方式替你选好、装好、体检好。GitHub 主仓库同步镜像。Python1284
new-apiAI模型聚合管理中转分发系统,一个应用管理您的所有AI模型,支持将多种大模型转为统一格式调用,支持OpenAI、Claude、Gemini等格式,可供个人或者企业内部管理与分发渠道使用。🍥 A Unified AI Model Management & Distribution System. Aggregate all your LLMs into one app and access them via an OpenAI-compatible API, with native support for Claude (Messages) and Gemini formats.Go23245
JeecgBoot🔥企业级低代码平台集成了AI应用平台,帮助企业快速实现低代码开发和构建AI应用!前后端分离架构 SpringBoot,SpringCloud、Mybatis,Ant Design4、 Vue3.0、TS+vite!强大的代码生成器让前后端代码一键生成,无需写任何代码! 引领AI低代码开发模式: AI生成->OnlineCoding-> 代码生成-> 手工MERGE,显著的提高效率,又不失灵活~Java37451