在前端开发或数据处理中,我们经常需要生成唯一标识符(ID)。标准的 Unix 时间戳(毫秒级)通常是一个 13 位的数字,比如 1703123456789。虽然这种格式便于排序和计算,但在某些场景下——如生成短链接、构建紧凑 ID、节省存储空间或提高 URL 可读性——我们希望将其变得更短。本文将介绍几种实用的字符串 ID 生成方法,并提供简洁高效的 JavaScript 实现。

为什么需要缩短时间戳?

时间戳本身具有全局唯一性和时序性,是非常理想的 ID 基础。但它的长度(13 位数字)在以下场景中显得冗长:

通过合理转换,我们可以将时间戳压缩到 6~8 个字符,同时保留其核心特性。

常见的缩短方法

使用高进制编码

JavaScript 的 Number.prototype.toString(radix) 方法支持 2~36 进制转换。进制越高,表示相同数值所需的字符越少:

const ts = Date.now();
console.log(ts.toString(16)); // 16进制,约11位
console.log(ts.toString(32)); // 32进制,约9位  
console.log(ts.toString(36)); // 36进制,约8位(推荐)

36 进制使用 0-9a-z 共 36 个字符,是原生支持的最高进制,兼顾简洁性和可读性。

减去基准时间戳

时间戳是从 1970 年开始计算的,但如果我们只关心近几年的数据,可以减去一个“基准时间”,只保留相对偏移量:

// 以2020年为基准
const base = new Date(2020, 0, 1).getTime();
const short = (Date.now() - base).toString(36);

这样能显著减少数值大小,从而进一步缩短编码长度。

组合策略:以当前年份为基准 + 36 进制

最实用的方案是以当前年份的开始时间为基准,再转为 36 进制。这既能大幅缩短长度,又保持了年内唯一性和时序性。

最简实现只需一行代码:

const short = (Date.now() - new Date(new Date().getFullYear(), 0)).toString(36);

这段代码:

  1. new Date().getFullYear() 获取当前年份
  2. new Date(年份, 0) 创建该年 1 月 1 日 00:00:00 的 Date 对象
  3. 相减得到当年内的毫秒偏移量
  4. .toString(36) 转为 36 进制字符串

结果通常为 6~7 个字符,例如:1k9j2m

重要提示:这种“减去当前年份”的方式仅在父级上下文已包含年份信息时才合适。例如:

  • 文件路径为 /2024/xxx,其中 xxx 使用年内偏移量
  • 数据库存储时有单独的 year 字段
  • URL 结构为 /api/2024/events/{shortId}

如果脱离年份上下文单独使用该 ID,则无法确定其真实时间,也无法保证跨年唯一性。

其他字符串 ID 生成方式

除了基于时间戳的方案,还有多种生成唯一字符串 ID 的方法:

纯随机字符串

生成指定长度的随机字符组合:

const randomId = Array.from({length: 8}, () => 
  'abcdefghijklmnopqrstuvwxyz0123456789'[Math.floor(Math.random() * 36)]
).join('');

优点是简单直接,缺点是无序且存在重复风险。

UUID

使用标准 UUID v4 格式(128 位):

constd uuid = crypto.randomUUID();

// 简易 UUID 实现
const uuid2 = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
  const r = Math.random() * 16 | 0;
  return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
// 去掉连字符可得 32 位十六进制字符串

UUID 全球唯一性极强,但长度较长(32 字符无连字符)。

雪花算法(Snowflake)

分布式 ID 生成算法,结合时间戳、机器 ID 和序列号,生成 64 位整数,通常转为字符串使用。长度固定(19 位数字),有序且全局唯一,但需服务端支持。

时间戳 + 随机后缀

结合时序性和随机性:

const id = Date.now().toString(36) + Math.random().toString(36).slice(2, 5);

既有时间序,又降低同一毫秒内重复的概率。

总结:字符串 ID 生成方案对比

方案 示例长度 字符集 有序性 重复概率 适用场景
原始时间戳 13 位 0-9 ✅ 完全有序 同一毫秒内重复 日志、排序
36 进制时间戳 8-9 位 0-9, a-z ✅ 完全有序 同一毫秒内重复 紧凑 ID、URL
年内偏移 + 36 进制 6-7 位 0-9, a-z ✅ 年内有序 同一毫秒内重复 需年份上下文,如文件名、子路径
8 位纯随机 8 位 自定义 ❌ 无序 中等(≈1/2820亿) 临时令牌、短链接
UUID(无连字符) 32 位 0-9, a-f ❌ 无序 极低(≈1/2^122) 全局唯一 ID、数据库主键
Snowflake ID 19 位 0-9 ✅ 完全有序 几乎为零 分布式系统、高并发场景
时间戳+随机后缀 10-12 位 0-9, a-z ✅ 基本有序 极低 通用唯一 ID,兼顾长度与唯一性

选择建议

记住:没有“最好”的方案,只有“最合适”当前场景的方案。根据你的业务需求、存储限制和唯一性要求,选择最平衡的实现方式。