如何在JavaScript中创建GUID(全局唯一标识符)?GUID / UUID应至少为32个字符,并应保持在ASCII范围内,以避免在传递时出现问题。
我不确定所有浏览器都可用哪些例程,内置的随机数生成器有多“随机”和有多少种子等信息。
如何在JavaScript中创建GUID(全局唯一标识符)?GUID / UUID应至少为32个字符,并应保持在ASCII范围内,以避免在传递时出现问题。
我不确定所有浏览器都可用哪些例程,内置的随机数生成器有多“随机”和有多少种子等信息。
[编辑于2023年3月5日,以反映生成符合RFC4122标准的UUID的最新最佳实践]
crypto.randomUUID()
现在已成为所有现代浏览器和JS运行时的标准。然而,由于新的浏览器API仅限于安全环境,该方法仅适用于本地服务的页面(localhost
或127.0.0.1
)或通过HTTPS提供的页面。
对于对其他UUID版本感兴趣的读者,在传统平台上生成UUID或在非安全环境中生成UUID,可以使用uuid
模块。该模块经过了充分测试并得到支持。
Failing the above, there is this method (based on the original answer to this question):
function uuidv4() {
return "10000000-1000-4000-8000-100000000000".replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
console.log(uuidv4());
[1e7]+-1e3
并没有实际意义,一个数组和一个数字相加?我可能错过了什么?请注意:在 TypeScript 中它不会通过。 - Ayyash<any>
,像这样:<any>[1e7]
- 快速通过的方法。 - NickyTheWrenchUUID(通用唯一识别码),也称为 GUID(全局唯一标识符),根据RFC 4122,是设计用于提供某些唯一性保证的标识符。
虽然可以通过几行 JavaScript 代码实现符合 RFC 的 UUID(如下面所示的@broofa的答案),但存在几个常见陷阱:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
”的格式,其中 x 是 [0-9、a-f] 中的一个,M 是 [1-5] 中的一个,N 是 [8、9、a 或 b] 中的一个)Math.random
)因此,编写用于生产环境的代码的开发人员应使用严格、维护良好的实现,如uuid模块。
我非常喜欢 Broofa的答案 的简洁性,但 由于Math.random
的不良实现存在碰撞的可能。
以下是一个类似的RFC4122版本4兼容的解决方案,通过将时间戳的前13个十六进制数偏移一个十六进制部分来解决此问题,并且一旦耗尽,则通过页面加载后的微秒数的十六进制部分进行偏移。即使 Math.random
处于相同的种子上,两个客户端也必须在页面加载后生成 UUID 的确切微秒数(如果支持高性能时间),并在确切的毫秒(或10000年后)生成UUID才能得到相同的 UUID:
function generateUUID() { // Public Domain/MIT
var d = new Date().getTime();//Timestamp
var d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now()*1000)) || 0;//Time in microseconds since page-load or 0 if unsupported
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random() * 16;//random number between 0 and 16
if(d > 0){//Use timestamp until depleted
r = (d + r)%16 | 0;
d = Math.floor(d/16);
} else {//Use microseconds since page-load if supported
r = (d2 + r)%16 | 0;
d2 = Math.floor(d2/16);
}
return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
});
}
var onClick = function(){
document.getElementById('uuid').textContent = generateUUID();
}
onClick();
#uuid { font-family: monospace; font-size: 1.5em; }
<p id="uuid"></p>
<button id="generateUUID" onclick="onClick();">Generate UUID</button>
const generateUUID = () => {
let
d = new Date().getTime(),
d2 = ((typeof performance !== 'undefined') && performance.now && (performance.now() * 1000)) || 0;
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => {
let r = Math.random() * 16;
if (d > 0) {
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
} else {
r = (d2 + r) % 16 | 0;
d2 = Math.floor(d2 / 16);
}
return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16);
});
};
const onClick = (e) => document.getElementById('uuid').textContent = generateUUID();
document.getElementById('generateUUID').addEventListener('click', onClick);
onClick();
#uuid { font-family: monospace; font-size: 1.5em; }
<p id="uuid"></p>
<button id="generateUUID">Generate UUID</button>
new Date().getTime()
并非每毫秒更新一次。我不确定这会如何影响您算法的预期随机性。 - devios1performance.now()
比Date.now
更好。performance.now()
返回的时间戳不仅限于毫秒分辨率,而是用浮点数表示时间,可以达到微秒级别的精度。与Date.now
不同的是,performance.now()
返回的值始终以恒定速率递增,不受系统时钟调整或由网络时间协议等软件引起的偏移影响。 - SavoryBytesvar d2 = (typeof performance !== 'undefined'..)
替换 var d2 = (performance..)
。另一个选项(实际上会利用 Node.js 的性能增强而不是浪费它)是在您的要求中重新添加 const {performance} = require('perf_hooks');
。 - Briguy37broofa的回答非常流畅,确实令人印象深刻 - 极其聪明,真的...符合RFC4122标准,有些易读且紧凑。太棒了!
但是,如果你看着那个正则表达式,那么多的replace()
回调函数,toString()
和Math.random()
函数调用(他只使用四位结果并浪费其余部分),你可能会开始担心性能。的确,joelpt甚至决定为通用GUID速度扔掉RFC,并使用generateQuickGUID
。
但是,我们可以同时获取速度和RFC兼容性吗?我的回答是,YES!我们能保持可读性吗?嗯...不是很好,但如果你跟随一起做就很容易。
但首先,与broofa、guid
(被接受的答案)和非RFC兼容的generateQuickGuid
相比,我的结果如下:
Desktop Android
broofa: 1617ms 12869ms
e1: 636ms 5778ms
e2: 606ms 4754ms
e3: 364ms 3003ms
e4: 329ms 2015ms
e5: 147ms 1156ms
e6: 146ms 1035ms
e7: 105ms 726ms
guid: 962ms 10762ms
generateQuickGuid: 292ms 2961ms
- Note: 500k iterations, results will vary by browser/CPU.
function broofa() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
}
console.log(broofa())
x
替换为任意随机十六进制数字,y
用随机数据替换(除了按照RFC规范强制使用前两位为10
),而正则表达式不匹配-
或4
字符,因此他不必处理它们。非常巧妙。-
和4
字符,而broofa没有处理。另外,请注意,我们可以使用字符串数组索引来保持他巧妙的字符串模板架构:
function e1() {
var u='',i=0;
while(i++<36) {
var c='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'[i-1],r=Math.random()*16|0,v=c=='x'?r:(r&0x3|0x8);
u+=(c=='-'||c=='4')?c:v.toString(16)
}
return u;
}
console.log(e1())
function e2() {
var u='',m='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx',i=0,rb=Math.random()*0xffffffff|0;
while(i++<36) {
var c=m[i-1],r=rb&0xf,v=c=='x'?r:(r&0x3|0x8);
u+=(c=='-'||c=='4')?c:v.toString(16);rb=i%8==0?Math.random()*0xffffffff|0:rb>>4
}
return u
}
console.log(e2())
function e3() {
var h='0123456789abcdef';
var k='xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx';
/* same as e4() below */
}
function e4() {
var h=['0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'];
var k=['x','x','x','x','x','x','x','x','-','x','x','x','x','-','4','x','x','x','-','y','x','x','x','-','x','x','x','x','x','x','x','x','x','x','x','x'];
var u='',i=0,rb=Math.random()*0xffffffff|0;
while(i++<36) {
var c=k[i-1],r=rb&0xf,v=c=='x'?r:(r&0x3|0x8);
u+=(c=='-'||c=='4')?c:h[v];rb=i%8==0?Math.random()*0xffffffff|0:rb>>4
}
return u
}
console.log(e4())
var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); }
function e5() {
var k=['x','x','x','x','-','x','x','-','4','x','-','y','x','-','x','x','x','x','x','x'];
var u='',i=0,rb=Math.random()*0xffffffff|0;
while(i++<20) {
var c=k[i-1],r=rb&0xff,v=c=='x'?r:(c=='y'?(r&0x3f|0x80):(r&0xf|0x40));
u+=(c=='-')?c:lut[v];rb=i%4==0?Math.random()*0xffffffff|0:rb>>8
}
return u
}
console.log(e5())
我尝试了一个e6()函数,它每次处理16位,仍然使用256个元素的LUT,但显示出优化效果递减的趋势。虽然迭代次数较少,但内部逻辑由于增加的处理而变得复杂,在桌面上表现相同,在移动设备上只快了约10%。
最后一种要应用的优化技术是展开循环。由于我们循环的次数是固定的,因此我们可以手工编写所有内容。我曾经试过使用单个随机变量r
来重复赋值,但性能下降了。但是如果预先分配四个变量的随机数据,然后使用查找表并应用正确的RFC位,这个版本就比其他版本更高效:
var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16); }
function e7()
{
var d0 = Math.random()*0xffffffff|0;
var d1 = Math.random()*0xffffffff|0;
var d2 = Math.random()*0xffffffff|0;
var d3 = Math.random()*0xffffffff|0;
return lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'-'+
lut[d1&0xff]+lut[d1>>8&0xff]+'-'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'-'+
lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'-'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+
lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff];
}
console.log(e7())
模块化:http://jcward.com/UUID.js - UUID.generate()
有趣的是,生成16字节的随机数据是容易的。整个技巧在于以RFC兼容的字符串格式表示它,最紧密的方法是使用16字节的随机数据、展开的循环和查找表。
我希望我的逻辑是正确的——在这种繁琐的位操作中很容易出错。但输出看起来对我来说很好。我希望你们喜欢这个通过代码优化的疯狂之旅!
请注意:我的主要目标是展示和教授潜在的优化策略。其他答案涵盖了重要的话题,例如碰撞和真正的随机数,这些对于生成良好的UUID非常重要。
Math.random()*0xFFFFFFFF
的行应该改为Math.random()*0x100000000
以获得完全随机性,使用>>>0
而不是|0
来保持值为无符号数(尽管在当前代码中,我认为它可以通过即使它们是有符号的)。最后,在可能的情况下最好使用window.crypto.getRandomValues
,只有在绝对必要时才回退到Math.random。Math.random可能具有少于128位的熵,如果是这样,相比必要的碰撞,这将更容易受到攻击。 - Dave使用:
let uniqueId = Date.now().toString(36) + Math.random().toString(36).substring(2);
document.getElementById("unique").innerHTML =
Math.random().toString(36).substring(2) + (new Date()).getTime().toString(36);
<div id="unique">
</div>
let u = Date.now().toString(16) + Math.random().toString(16) + '0'.repeat(16);
let guid = [u.substr(0,8), u.substr(8,4), '4000-8' + u.substr(13,3), u.substr(16,12)].join('-');
let u = Date.now().toString(16)+Math.random().toString(16)+'0'.repeat(16);
let guid = [u.substr(0,8), u.substr(8,4), '4000-8' + u.substr(13,3), u.substr(16,12)].join('-');
document.getElementById("unique").innerHTML = guid;
<div id="unique">
</div>
以上代码虽然符合意图,但并不完全符合RFC的要求。其中有一些差异是随机数字不够多。(如果需要,可以添加更多随机数字)好处是这样做非常快速 :) 您可以在此处测试您的GUID的有效性
[slug,date,random] .join("_")
),以创建usr_1dcn27itd_hj6onj6phr
。这样可以使ID也兼作“创建于”字段。 - Seph ReedtoString(36)
将数字转换为 36 进制数(0..9a..z)。例如:(35).toString(36)
是 z
。 - Basj这里有一些基于RFC 4122第4.4节(从真正随机或伪随机数字创建UUID的算法)的代码。
function createUUID() {
// http://www.ietf.org/rfc/rfc4122.txt
var s = [];
var hexDigits = "0123456789abcdef";
for (var i = 0; i < 36; i++) {
s[i] = hexDigits.substr(Math.floor(Math.random() * 0x10), 1);
}
s[14] = "4"; // bits 12-15 of the time_hi_and_version field to 0010
s[19] = hexDigits.substr((s[19] & 0x3) | 0x8, 1); // bits 6-7 of the clock_seq_hi_and_reserved to 01
s[8] = s[13] = s[18] = s[23] = "-";
var uuid = s.join("");
return uuid;
}
var s = new Array(36);
- MgSam这是一种类似于GUID的字符串生成方法,其格式为XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
。它不能生成符合标准的GUID。
执行该实现的一千万次只需要32.5秒,这是我在浏览器中见过的最快的方法(没有循环/迭代的唯一解决方案)。
函数非常简单:
/**
* Generates a GUID string.
* @returns {string} The generated GUID.
* @example af8a8416-6e18-a307-bd9c-f2c947bbb3aa
* @author Slavik Meltser.
* @link http://slavik.meltser.info/?p=142
*/
function guid() {
function _p8(s) {
var p = (Math.random().toString(16)+"000000000").substr(2,8);
return s ? "-" + p.substr(0,4) + "-" + p.substr(4,4) : p ;
}
return _p8() + _p8(true) + _p8(true) + _p8();
}
console.time('t');
for (var i = 0; i < 10000000; i++) {
guid();
};
console.timeEnd('t');
我相信你们大多数人都能理解我在做什么,但可能还有至少一个人需要解释:
算法:
Math.random()
函数返回0到1之间的十六位小数(例如0.4363923368509859
)。0.6fb7687f
)。
Math.random().toString(16)
。0.
(0.6fb7687f
=>6fb7687f
),得到一个包含八个十六进制字符的字符串。
(Math.random().toString(16).substr(2,8)
。Math.random()
函数会返回较短的数字(例如0.4363
),原因是末尾有零(从上面的示例中,实际上数字是0.4363000000000000
)。这就是为什么我要将字符串追加"000000000"
(九个零的字符串),然后用substr()
函数截断它,使其恰好为九个字符(向右填充零)。Math.random()
函数恰好返回0或1(每个数字的概率为1/10^16)。这就是我们需要向它添加九个零的原因("0"+"000000000"
或"1"+"000000000"
),然后从第二个索引(第三个字符)开始截取八个字符长度。对于其他情况,添加零不会影响结果,因为它仍然会被截断。
(Math.random().toString(16)+"000000000").substr(2,8)
。汇编语言:
XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
。XXXXXXXX
和 -XXXX-XXXX
。XXXXXXXX
-XXXX-XXXX
-XXXX-XXXX
XXXXXXXX
。_p8(s)
添加了一个标志参数。参数 s
告诉函数是否添加破折号。_p8() + _p8(true) + _p8(true) + _p8()
,并返回它。愉快地编程!:-)
${_p8()}${_p8()}${_p8()}${_p8()}
;function generateQuickGuid() {
return Math.random().toString(36).substring(2, 15) +
Math.random().toString(36).substring(2, 15);
}
生成26个[a-z0-9]字符,产生的UID比符合RFC标准的GUID更短更唯一。如果需要人类可读性,则可以轻松添加破折号。
以下是此功能以及本问题其他答案的使用示例和定时。时间在Chrome m25下执行,每个迭代1000万次。
>>> generateQuickGuid()
"nvcjf1hs7tf8yyk4lmlijqkuo9"
"yq6gipxqta4kui8z05tgh9qeel"
"36dh5sec7zdj90sk2rx7pjswi2"
runtime: 32.5s
>>> GUID() // John Millikin
"7a342ca2-e79f-528e-6302-8f901b0b6888"
runtime: 57.8s
>>> regexGuid() // broofa
"396e0c46-09e4-4b19-97db-bd423774a4b3"
runtime: 91.2s
>>> createUUID() // Kevin Hakanson
"403aa1ab-9f70-44ec-bc08-5d5ac56bd8a5"
runtime: 65.9s
>>> UUIDv4() // Jed Schmidt
"f4d7d31f-fa83-431a-b30c-3e6cc37cc6ee"
runtime: 282.4s
>>> Math.uuid() // broofa
"5BD52F55-E68F-40FC-93C2-90EE069CE545"
runtime: 225.8s
>>> Math.uuidFast() // broofa
"6CB97A68-23A2-473E-B75B-11263781BBE6"
runtime: 92.0s
>>> Math.uuidCompact() // broofa
"3d7b7a06-0a67-4b67-825c-e5c43ff8c1e8"
runtime: 229.0s
>>> bitwiseGUID() // jablko
"baeaa2f-7587-4ff1-af23-eeab3e92"
runtime: 79.6s
>>>> betterWayGUID() // Andrea Turri
"383585b0-9753-498d-99c3-416582e9662c"
runtime: 60.0s
>>>> UUID() // John Fowler
"855f997b-4369-4cdb-b7c9-7142ceaf39e8"
runtime: 62.2s
这里是计时代码。
var r;
console.time('t');
for (var i = 0; i < 10000000; i++) {
r = FuncToTest();
};
console.timeEnd('t');
function generateGuid() {
var result, i, j;
result = '';
for(j=0; j<32; j++) {
if( j == 8 || j == 12 || j == 16 || j == 20)
result = result + '-';
i = Math.floor(Math.random()*16).toString(16).toUpperCase();
result = result + i;
}
return result;
}
有其他使用 ActiveX 控件的方法,但是请避免使用这些方法!
需要指出的是,没有GUID生成器能够保证唯一键(请查看维基百科文章)。总会存在碰撞的可能性。 GUID仅仅提供了足够大的键空间,以将碰撞的几率降低到几乎为零。
这是将得票最多的答案与Chrome的碰撞回避方法相结合的方案:
generateGUID = (typeof(window.crypto) != 'undefined' &&
typeof(window.crypto.getRandomValues) != 'undefined') ?
function() {
// If we have a cryptographically secure PRNG, use that
// https://dev59.com/XGw05IYBdhLWcg3w_Guw
var buf = new Uint16Array(8);
window.crypto.getRandomValues(buf);
var S4 = function(num) {
var ret = num.toString(16);
while(ret.length < 4){
ret = "0"+ret;
}
return ret;
};
return (S4(buf[0])+S4(buf[1])+"-"+S4(buf[2])+"-"+S4(buf[3])+"-"+S4(buf[4])+"-"+S4(buf[5])+S4(buf[6])+S4(buf[7]));
}
:
function() {
// Otherwise, just use Math.random
// https://dev59.com/7HVD5IYBdhLWcg3wDXF3#2117523
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
};
如果你想测试它,在jsbin上可以找到它。
window.crypto.getRandomValues
生成的UUID格式不符合RFC 4122中定义的Version 4 UUIDs格式。它产生的格式为xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
,而不是xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
。 - humanityANDpeace
BigInt
和ES6类以及其他技术,可以实现每秒500,000个uuid的速率。参见参考链接 - smallscriptURL.createObjectURL(new Blob()).substr(-36)
即可。该方法得到了很好的浏览器支持。为避免内存泄漏,需要调用URL.revokeObjectURL(url)
。 - rinogo