如何创建GUID / UUID?

5319

如何在JavaScript中创建GUID(全局唯一标识符)?GUID / UUID应至少为32个字符,并应保持在ASCII范围内,以避免在传递时出现问题。

我不确定所有浏览器都可用哪些例程,内置的随机数生成器有多“随机”和有多少种子等信息。


38
GUIDs表示为字符串时,长度至少为36个字符,最多不超过38个字符,并匹配模式^{?[a-zA-Z0-9]{36}?}$,因此始终为ASCII码。 - AnthonyWJones
5
David Bau在http://davidbau.com/archives/2010/01/30/random_seeds_coded_hints_and_quintillions.html提供了一个更好的、可初始化的随机数生成器。我在http://blogs.cozi.com/tech/2010/04/generating-uuids-in-javascript.html中记录了一种略微不同的生成UUID的方法。 - George V. Reilly
1
很奇怪还没有人提到这一点,但为了完整起见,在npm上有大量的guid生成器。我敢打赌它们中的大多数也可以在浏览器中使用。 - George Mauer
2
12年后,使用BigInt和ES6类以及其他技术,可以实现每秒500,000个uuid的速率。参见参考链接 - smallscript
5
如其他人所提到的,如果您只在浏览器中生成少量uuid,则只需使用URL.createObjectURL(new Blob()).substr(-36)即可。该方法得到了很好的浏览器支持。为避免内存泄漏,需要调用 URL.revokeObjectURL(url) - rinogo
显示剩余2条评论
73个回答

5517

[编辑于2023年3月5日,以反映生成符合RFC4122标准的UUID的最新最佳实践]

crypto.randomUUID() 现在已成为所有现代浏览器和JS运行时的标准。然而,由于新的浏览器API仅限于安全环境,该方法仅适用于本地服务的页面(localhost127.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());

注意:强烈不建议使用任何依赖于Math.random()的UUID生成器(包括此答案先前版本中的代码片段),原因可以在这里找到最好的解释。简而言之,基于Math.random()的解决方案无法提供良好的唯一性保证。

176
@Muxa的问题的回答肯定是“不”,从客户端获得的东西永远不是真正安全可靠的。我猜这取决于你的用户有多可能打开javascript控制台并手动更改变量为他们想要的值,或者他们可以通过POST方式将他们想要的ID发送回给您。这还取决于用户选择自己的ID是否会导致漏洞。无论如何,如果是一个随机数字ID进入表格,我通常会在服务器端生成它,这样我就知道我对整个过程有控制权。 - Cam Jackson
49
UUID不仅是一串完全随机的数字。其中的“4”表示UUID的版本(4表示“随机”)。而“y”标记了需要嵌入UUID变体(字段布局)的位置。有关更多信息,请参阅http://www.ietf.org/rfc/rfc4122.txt的4.1.1和4.1.3节。 - broofa
10
我知道你在帖子中加入了很多警告,但最好现在删掉第一个答案,因为很多新手只会复制他们看到的第一件事而不阅读其余部分。实际上,从Math.random API生成可靠的UUID是不可靠的,并且依靠它可能是危险的。 - hPNJ7MHTyg
22
有点困惑,在 JavaScript 中,[1e7]+-1e3 并没有实际意义,一个数组和一个数字相加?我可能错过了什么?请注意:在 TypeScript 中它不会通过。 - Ayyash
12
Typescript用户:你可以在第一个数组前加上<any>,像这样:<any>[1e7] - 快速通过的方法。 - NickyTheWrench
显示剩余15条评论

2623

UUID(通用唯一识别码),也称为 GUID(全局唯一标识符),根据RFC 4122,是设计用于提供某些唯一性保证的标识符。

虽然可以通过几行 JavaScript 代码实现符合 RFC 的 UUID(如下面所示的@broofa的答案),但存在几个常见陷阱:

  • 无效的 id 格式(UUID 必须采用“xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx”的格式,其中 x 是 [0-9、a-f] 中的一个,M 是 [1-5] 中的一个,N 是 [8、9、a 或 b] 中的一个)
  • 使用低质量的随机源(例如 Math.random

因此,编写用于生产环境的代码的开发人员应使用严格、维护良好的实现,如uuid模块。


212
实际上,RFC允许使用随机数创建UUID。只需要调整几个位来标识它是这样创建的。请参阅第4.4节“从真正随机或伪随机数创建UUID的算法”:http://www.rfc-archive.org/getrfc.php?rfc=4122 - Jason DeFontes
69
这不应该是被接受的答案。它实际上没有回答问题,反而鼓励导入 25,000 行代码来完成在任何现代浏览器中只需一行代码即可完成的操作。 - Abhi Beckert
1
@AbhiBeckert 这个答案是来自2008年的,对于Node.js项目来说,选择依赖项可能比项目大小更为重要。 - Phil
9
@Phil 这是一个“高活跃度的问题”,这意味着它应该有一个优秀的答案并标记为绿色。不幸的是,情况并非如此。这个回答没有错误或不正确(如果有的话,我将编辑此回答),但是下面存在另一个更好的答案,我认为它应该排在列表的顶部。此外,这个问题特别涉及浏览器中的JavaScript,而不是Node.js。 - Abhi Beckert
3
我挑战声称Math.random随机性差的说法。您可以看到,它通过了很好的测试套件,并且v8、FF和Safari都使用相同的算法。而RFC声明,伪随机数对于UUID是可接受的。 - Munawwar
显示剩余9条评论

999

我非常喜欢 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>

这里是一个测试用的代码片段。


现代化的ES6代码片段

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>


38
请记住,new Date().getTime()并非每毫秒更新一次。我不确定这会如何影响您算法的预期随机性。 - devios1
95
performance.now()Date.now更好。performance.now()返回的时间戳不仅限于毫秒分辨率,而是用浮点数表示时间,可以达到微秒级别的精度。与Date.now不同的是,performance.now()返回的值始终以恒定速率递增,不受系统时钟调整或由网络时间协议等软件引起的偏移影响。 - SavoryBytes
我是JavaScript的新手。当我在Node 14上运行ES6版本时,我会得到“ReferenceError:performance未定义”的错误。我该怎么解决它? - Naveen Reddy Marthala
3
默认情况下,Node.js 在严格模式下运行 JavaScript,不幸的是,这种模式不允许布尔逻辑符号来检查未定义变量的真实性。为了解决这个问题,请尝试使用更新版本中的 var d2 = (typeof performance !== 'undefined'..) 替换 var d2 = (performance..)。另一个选项(实际上会利用 Node.js 的性能增强而不是浪费它)是在您的要求中重新添加 const {performance} = require('perf_hooks'); - Briguy37
它在Google应用脚本上起作用了。谢谢! - JRichardsz
显示剩余7条评论

547

broofa的回答非常流畅,确实令人印象深刻 - 极其聪明,真的...符合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.

在我的第六次优化中,我打败了最受欢迎的答案超过12倍,超过了被接受的答案9倍,而快速非兼容答案则是2-3倍。而且我仍然符合RFC 4122标准。
想知道如何实现吗?我已经将完整源代码放在http://jsfiddle.net/jcward/7hyaC/3/https://jsben.ch/xczxS上。
关于解释,请从broofa的代码开始:

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字符,因此他不必处理它们。非常巧妙。
首先要知道的是函数调用很昂贵,正则表达式也很昂贵(虽然他只使用了1个,但它有32个回调,每个回调对应一个匹配,在每个32个回调中都会调用Math.random()和v.toString(16))。
提高性能的第一步是消除RegEx及其回调函数,改用简单的循环。这意味着我们必须处理-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())

基本上,内部逻辑相同,只是我们检查“-”或“4”,并使用while循环(而不是replace()回调)可以使我们的性能提高近3倍!下一步在桌面上只是一个小步骤,但在移动设备上会有很大的差别。让我们减少Math.random()的调用次数,并利用所有这些随机位,而不是通过每次迭代都被移出的随机缓冲区丢弃87%的随机位。同时,让我们将该模板定义移到循环外部,以防万一它有所帮助:

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())

这可以为我们节省10-30%的成本,具体取决于平台。不错。但是下一步的重要进展是使用优化经典方法——查找表来完全消除toString函数调用。一个简单的16元素查找表将在更短的时间内执行toString(16)的任务:

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())

下一个优化是另一个经典案例。由于我们每次循环只处理四位输出,让我们将循环次数减半,并在每次迭代中处理八位。这有点棘手,因为我们仍然必须处理符合RFC的位位置,但这不太难。然后,我们必须制作一个更大的查找表(16x16或256)来存储0x00 - 0xFF,并且我们仅在e5()函数之外构建它一次。

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非常重要。


26
这段代码仍然存在一些错误:Math.random()*0xFFFFFFFF的行应该改为Math.random()*0x100000000以获得完全随机性,使用>>>0而不是|0来保持值为无符号数(尽管在当前代码中,我认为它可以通过即使它们是有符号的)。最后,在可能的情况下最好使用window.crypto.getRandomValues,只有在绝对必要时才回退到Math.random。Math.random可能具有少于128位的熵,如果是这样,相比必要的碰撞,这将更容易受到攻击。 - Dave
17
我可以说——我无法计算有多少次我把这个答案发给开发人员,因为它非常好地指出了性能、代码优雅和可读性之间的权衡。谢谢Jeff。 - Nemesarial
@Andy是正确的。截至2021年8月,Broofa的代码更快。我实施了Dave的建议并自己运行了测试。但我不认为在生产中这种差异会有太大影响:https://jsbench.github.io/#80610cde9bc93d0f3068e5793e60ff11 - Doomd
1
@bedalton:为什么我们要将broofa的答案与“e4版本”进行比较?在e4中,“4”只是指优化的迭代次数,而不是UUID的版本,对吧? - rinogo
我没有听清楚。 - bedalton
显示剩余3条评论

222

使用:

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>

如果生成的ID间隔超过1毫秒,它们将是100%唯一的。
如果在较短时间内生成两个ID,并且假设随机方法是真正随机的,则会生成99.99999999999999%可能是全局唯一的ID(10 ^ 15中有1个碰撞)。
您可以通过添加更多数字来增加此数字,但要生成100%唯一的ID,您需要使用全局计数器。
如果您需要RFC兼容性,则此格式将作为有效的版本4 GUID传递。
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的有效性


9
这不是UUID吗? - Marco Kerwitz
12
仅依赖MAC地址保证虚拟机的唯一性是不明智的做法! - Simon Rigét
1
我会做类似这样的事情,但是使用前导字符和一些破折号(例如[slug,date,random] .join("_")),以创建usr_1dcn27itd_hj6onj6phr。这样可以使ID也兼作“创建于”字段。 - Seph Reed
1
在 @SephReed 的评论基础上,我认为将日期部分放在前面很好,因为它按时间顺序排序,如果存储或索引 ID 可能会带来后续的好处。 - totalhack
1
对于那些好奇的人:toString(36) 将数字转换为 36 进制数(0..9a..z)。例如:(35).toString(36)z - Basj
显示剩余4条评论

191

这里有一些基于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;
}

6
在构建GUID时,你应该事先声明数组大小而不是动态调整大小。 var s = new Array(36); - MgSam
2
我认为在将时钟序列高位保留的位的6-7位设置为01的行中存在一个非常小的错误。由于s [19]是字符'0'..'f'而不是int 0x0..0xf,因此(s [19]&0x3)| 0x8不会随机分布 - 它倾向于产生更多的“9”和较少的“b”。只有在某些情况下您关心随机分布时,这才有所区别。 - John Velonis

105

这是一种类似于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)

汇编语言:

  • GUID 的格式如下:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  • 我把 GUID 分成了四个部分,每个部分都分为两种类型(或格式):XXXXXXXX-XXXX-XXXX
  • 现在我正在使用这两种类型来组装 GUID,以拼接所有四个部分,如下所示:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
  • 为了区分这两种类型,我向一对创建函数 _p8(s) 添加了一个标志参数。参数 s 告诉函数是否添加破折号。
  • 最终我们使用以下链接方式构建 GUID:_p8() + _p8(true) + _p8(true) + _p8(),并返回它。

转到我的博客阅读原文

愉快地编程!:-)


17
这个实现是有问题的。GUID 中的某些字符需要特殊处理(例如,第13位数字必须是数字4)。 - JLRishe
稍作修改,使用箭头函数和 toString 替代 toStr(已弃用)。同时去掉了连字符!
guid = () => {
_p8 = () => {return (Math.random() * 10000000000).toString(16).substr(0,8);}
return ${_p8()}${_p8()}${_p8()}${_p8()};
};
- Peter Korinek TellusTalk

84
这是一种完全不符合规范但非常高效的实现方式,用于生成类似GUID的ASCII安全唯一标识符。
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');

不确定在过去的10年里实现是否有所改变,但是对我来说,Math.random()产生的数字并不像对你产生的那样多--对我来说,Math.random().toString(36).substring(2, 15)只有10或11个字符。 - undefined

78

来自Sagi Shkedy的技术博客

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仅仅提供了足够大的键空间,以将碰撞的几率降低到几乎为零。


10
请注意,从技术角度来看,这不是GUID,因为它不能保证唯一性。这可能或可能不重要,具体取决于您的应用程序。 - Stephen Deken
3
关于性能的简短说明。这种方法总共创建了36个字符串来得到一个结果。如果性能是至关重要的,请考虑创建一个数组并按推荐方式连接。http://tinyurl.com/y37xtx进一步的研究表明这可能并不重要,所以具体情况具体分析。http://tinyurl.com/3l7945 - Brandon DuRette
2
关于唯一性,值得注意的是版本1、3和5 UUID在某些方面是确定性的,而版本4则不是。如果这些uuid生成器的输入——v1中的节点ID、v3和v5中的命名空间和名称——是唯一的(正如它们应该是的),那么生成的UUID将是唯一的。无论如何,在理论上是这样的。 - broofa
@DanielMarschall,这并不会生成UUIDs,但会生成有效的GUIDs,它们在2008年这个答案被编写时在Microsoft代码(如.Net)中很常见。请注意,这也是十六进制字符强制大写的原因。请参阅:https://learn.microsoft.com/en-us/windows/win32/msi/guid - Prestaul
@DanielMarschall,您说得对,这不符合UUID/GUID规范。我只是在说明,在2008年编写此代码时,GUID和UUID并不意味着相同的东西,微软有自己的GUID定义和实现,这在相当广泛的使用中。该定义(您看到我分享的链接了吗)其中没有版本/变体。 - Prestaul
显示剩余3条评论

70

这是将得票最多的答案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上可以找到它。


3
请注意,第一个版本window.crypto.getRandomValues生成的UUID格式不符合RFC 4122中定义的Version 4 UUIDs格式。它产生的格式为xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,而不是xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx - humanityANDpeace

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接