一行代碼生成絕對(duì)唯一 ID?別再用 Date.now() 了 !
我們總會(huì)遇到需要生成“唯一ID”的場(chǎng)景,“唯一ID”這個(gè)需求看似簡(jiǎn)單,但要實(shí)現(xiàn)一個(gè)絕對(duì)不會(huì)重復(fù)的 ID,卻比想象中要復(fù)雜。
誤區(qū)一:嘗試 (Date.now() + Math.random())
很多初學(xué)者(甚至一些老手)的直覺(jué)反應(yīng)是:時(shí)間戳 + 隨機(jī)數(shù)。
function generateNaiveId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2);
}
// 示例輸出: "l6n7f4v2am50k9m7o4"
這個(gè)方法看起來(lái)不錯(cuò),結(jié)合了時(shí)間的唯一性和隨機(jī)性。但在高并發(fā)或快速操作的場(chǎng)景下,它的“絕對(duì)唯一”承諾不堪一擊:
- 時(shí)間戳精度問(wèn)題:Date.now() 的精度是毫秒,如果在同一毫秒內(nèi)調(diào)用兩次 generateNaiveId(),ID 的前半部分就會(huì)完全一樣
- 偽隨機(jī)性:Math.random() 產(chǎn)生的不是真正的“加密級(jí)”隨機(jī)數(shù),在極小的概率下,它也可能在短時(shí)間內(nèi)生成重復(fù)的序列
結(jié)論: 這種方法在低頻次場(chǎng)景下“似乎”可用,但它離“絕對(duì)唯一”相去甚遠(yuǎn),是生產(chǎn)環(huán)境中的一顆定時(shí)炸彈。
誤區(qū)二:簡(jiǎn)單的自增計(jì)數(shù)器
另一個(gè)思路是維護(hù)一個(gè)全局計(jì)數(shù)器。
這個(gè)方案的缺陷更加明顯:
- 無(wú)狀態(tài)性:瀏覽器環(huán)境是無(wú)狀態(tài)的,用戶一刷新頁(yè)面,counter 就重置為 0
- 多標(biāo)簽頁(yè)沖突:用戶打開(kāi)兩個(gè)相同的頁(yè)面,每個(gè)頁(yè)面都有一個(gè)獨(dú)立的 counter,它們會(huì)從 0 開(kāi)始生成完全相同的 ID 序列,導(dǎo)致立刻沖突
結(jié)論: 純粹的自增計(jì)數(shù)器方案,在瀏覽器環(huán)境中幾乎沒(méi)有任何實(shí)用價(jià)值。
擁抱密碼學(xué)和標(biāo)準(zhǔn)
既然簡(jiǎn)單的方法都行不通,我們需要更可靠、更科學(xué)的武器。幸運(yùn)的是,瀏覽器(Node.js14+)已經(jīng)為我們內(nèi)置了這樣的武器。
王者方案:crypto.randomUUID()
這是 W3C 標(biāo)準(zhǔn)和現(xiàn)代瀏覽器提供的官方解決方案。crypto 是一個(gè)瀏覽器內(nèi)置的全局對(duì)象,提供了加密相關(guān)的能力,而 randomUUID() 方法專門用于生成一個(gè)符合 RFC 4122 v4 規(guī)范的通用唯一標(biāo)識(shí)符(UUID)。
const uniqueId = crypto.randomUUID();
// 示例輸出: "3a6c4b2a-4c26-4d0f-a4b7-3b1a2b3c4d5e"
為什么 crypto.randomUUID() 是王者?
- 極低的碰撞概率:一個(gè) v4 UUID 是由 122 位的隨機(jī)數(shù)生成的,其組合數(shù)量是一個(gè)天文數(shù)字,碰撞概率趨近于零
- 加密級(jí)安全:它使用密碼學(xué)安全偽隨機(jī)數(shù)生成器(CSPRNG),其隨機(jī)性遠(yuǎn)非 Math.random() 可比,無(wú)法被預(yù)測(cè)
- 標(biāo)準(zhǔn)化:它生成的是全球公認(rèn)的標(biāo)準(zhǔn)格式,無(wú)論前端、后端還是數(shù)據(jù)庫(kù),都能識(shí)別和處理
- 原生、簡(jiǎn)單、高效:無(wú)需引入任何第三方庫(kù),一行代碼即可調(diào)用,性能極高
crypto.randomUUID() 已經(jīng)得到了所有現(xiàn)代主流瀏覽器的支持(Chrome 92+, Firefox 90+, Safari 15.4+, Node.js14+)。對(duì)于絕大多數(shù)新項(xiàng)目而言,可以放心使用。