我正在尝试在 JavaScript 中创建全局唯一标识符。我不确定所有浏览器上都有哪些例程,内置随机数生成器的“随机性”和播种程度等。
GUID / UUID 应至少为 32 个字符,并且应保持在 ASCII 范围内,以避免在传递它们时出现问题。
我正在尝试在 JavaScript 中创建全局唯一标识符。我不确定所有浏览器上都有哪些例程,内置随机数生成器的“随机性”和播种程度等。
GUID / UUID 应至少为 32 个字符,并且应保持在 ASCII 范围内,以避免在传递它们时出现问题。
[于 2021 年 10 月 16 日编辑,以反映生产符合 RFC4122 的 UUID 的最新最佳实践]
这里的大多数读者都想使用该uuid
模块。它经过了良好的测试和支持。
该crypto.randomUUID()
功能是一种新兴标准,越来越多的浏览器支持Node.js
该标准。
如果这些都不适合你,有这个方法(基于这个问题的原始答案):
function uuidv4() {
return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
}
console.log(uuidv4());
注意:强烈建议不要使用任何依赖 Math.random() 的 UUID 生成器(包括此答案的先前版本中的片段),原因在此处进行了最佳解释。TL;DR:基于 Math.random() 的解决方案不能提供良好的唯一性保证。
UUID(通用唯一标识符),也称为 GUID(全局唯一标识符),根据RFC 4122,是旨在提供某些唯一性保证的标识符。
虽然可以在几行 JavaScript 代码中实现符合 RFC 的 UUID(例如,请参阅下面的 @broofa 的答案),但有几个常见的陷阱:
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx
”,其中 x 是 [0-9,af]之一, M是 [1-5] 之一,N是 [8、9、a 或 b]Math.random
)因此,鼓励为生产环境编写代码的开发人员使用严格的、维护良好的实现,例如uuid模块。
我真的很喜欢Broofa 的答案是多么干净,但不幸的是,糟糕的实现Math.random
留下了碰撞的机会。
这是一个类似的符合RFC4122第 4 版的解决方案,它通过将前 13 个十六进制数字偏移时间戳的十六进制部分来解决该问题,并在页面加载后以微秒的十六进制部分耗尽偏移量。这样,即使Math.random
在同一个种子上,两个客户端也必须在页面加载后(如果支持高性能时间)和完全相同的毫秒(或 10,000 多年后)生成 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 = (performance && 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>
broofa 的答案确实非常巧妙 - 非常聪明,真的...... 符合 RFC4122,有点可读性和紧凑性。惊人的!
但是,如果您正在查看那个正则表达式、那些许多replace()
回调、toString()
's 和Math.random()
函数调用(他只使用了四位结果并浪费了其余部分),您可能会开始怀疑性能。事实上,joelpt 甚至决定放弃一个 RFC 以实现通用 GUID 速度generateQuickGUID
。
但是,我们能否获得速度和RFC 合规性?我说是!我们可以保持可读性吗?嗯......不是真的,但如果你跟着它很容易。
但首先,与 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.
因此,在我的第 6 次优化迭代中,我击败了最受欢迎的答案超过12 倍,超过了接受的答案超过了9 倍,以及快速不合规的答案超过了2-3 倍。而且我仍然符合 RFC 4122。
有兴趣怎么做?我已经把完整的源代码放在http://jsfiddle.net/jcward/7hyaC/3/和http://jsperf.com/uuid-generator-opt/4
为了解释,让我们从 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 规范强制前两位),并且正则表达式与or字符10
不匹配,所以他不必处理它们。非常非常光滑。-
4
首先要知道的是函数调用很昂贵,正则表达式也是如此(尽管他只使用了 1 个,但它有 32 个回调,每个匹配一个,并且在 32 个回调中的每一个中它调用 Math.random() 和 v。 toString(16))。
提高性能的第一步是消除 RegEx 及其回调函数,并改用简单的循环。这意味着我们必须处理-
and4
字符,而 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())
基本上,相同的内部逻辑,除了我们检查-
or 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())
下一个优化是另一个经典。由于我们在每次循环迭代中只处理 4 位输出,所以让我们将循环数减半并在每次迭代中处理 8 位。这很棘手,因为我们仍然必须处理符合 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())
我尝试了一次处理 16 位的 e6(),仍然使用 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 很重要。
采用:
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 的有效性
这是一些基于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;
}
这是格式中最快的类似 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 之间的十进制数,小数点后有 16 位(例如0.4363923368509859
)。0.6fb7687f
)。
Math.random().toString(16)
.0.
前缀 ( 0.6fb7687f
=>
6fb7687f
) 并得到一个长度为 8 个十六进制字符的字符串。
(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()
,并返回它。享受!:-)
这是一个完全不兼容但非常高效的实现,用于生成一个 ASCII 安全的类似 GUID 的唯一标识符。
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 生成器可以保证唯一的键(查看Wikipedia 文章)。总是有碰撞的可能。GUID 只是提供足够大的键域来将冲突的变化减少到几乎为零。
这是投票最多的答案的组合,以及Chrome 冲突的解决方法:
generateGUID = (typeof(window.crypto) != 'undefined' &&
typeof(window.crypto.getRandomValues) != 'undefined') ?
function() {
// If we have a cryptographically secure PRNG, use that
// https://stackoverflow.com/questions/6906916/collisions-when-generating-uuids-in-javascript
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://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/2117523#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 上。
这是 2011 年 10 月 9 日用户jed在https://gist.github.com/982883的评论中的解决方案:
UUIDv4 = function b(a){return a?(a^Math.random()*16>>a/4).toString(16):([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,b)}
这实现了与当前评分最高的答案相同的目标,但通过利用强制、递归和指数表示法减少了 50 多个字节。对于那些好奇它是如何工作的人,这里是该函数旧版本的注释形式:
UUIDv4 =
function b(
a // placeholder
){
return a // if the placeholder was passed, return
? ( // a random number from 0 to 15
a ^ // unless b is 8,
Math.random() // in which case
* 16 // a random number from
>> a/4 // 8 to 11
).toString(16) // in hexadecimal
: ( // or otherwise a concatenated string:
[1e7] + // 10000000 +
-1e3 + // -1000 +
-4e3 + // -4000 +
-8e3 + // -80000000 +
-1e11 // -100000000000,
).replace( // replacing
/[018]/g, // zeroes, ones, and eights with
b // random hex digits
)
}
您可以使用node-uuid。它提供了简单、快速的RFC4122 UUIDS 生成。
特征:
使用 NPM 安装:
npm install uuid
或者通过浏览器使用 uuid:
下载原始文件(uuid v1):https ://raw.githubusercontent.com/kelektiv/node-uuid/master/v1.js 下载原始文件(uuid v4):https ://raw.githubusercontent.com/kelektiv/node -uuid/master/v4.js
想要更小?看看这个:https ://gist.github.com/jed/982883
用法:
// Generate a v1 UUID (time-based)
const uuidV1 = require('uuid/v1');
uuidV1(); // -> '6c84fb90-12c4-11e1-840d-7b25c5ee775a'
// Generate a v4 UUID (random)
const uuidV4 = require('uuid/v4');
uuidV4(); // -> '110ec58a-a0f2-4ac4-8393-c866d813b8d1'
// Generate a v5 UUID (namespace)
const uuidV5 = require('uuid/v5');
// ... using predefined DNS namespace (for domain names)
uuidV5('hello.example.com', v5.DNS)); // -> 'fdda765f-fc57-5604-a269-52a7df8164ec'
// ... using predefined URL namespace (for, well, URLs)
uuidV5('http://example.com/hello', v5.URL); // -> '3bbcee75-cecc-5b56-8031-b6641c1ed1f1'
// ... using a custom namespace
const MY_NAMESPACE = '(previously generated unique uuid string)';
uuidV5('hello', MY_NAMESPACE); // -> '90123e1c-7512-523e-bb28-76fab9f2f73d'
ECMAScript 2015 (ES6):
import uuid from 'uuid/v4';
const id = uuid();
var uuid = function() {
var buf = new Uint32Array(4);
window.crypto.getRandomValues(buf);
var idx = -1;
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
idx++;
var r = (buf[idx>>3] >> ((idx%8)*4))&15;
var v = c == 'x' ? r : (r&0x3|0x8);
return v.toString(16);
});
};
此版本基于 Briguy37 的答案和一些按位运算符从缓冲区中提取半字节大小的窗口。
它应该遵守 RFC Type 4(随机)模式,因为我上次使用 Java 的 UUID 解析不兼容的 UUID 时遇到了问题。
这将创建一个版本 4 UUID(从伪随机数创建):
function uuid()
{
var chars = '0123456789abcdef'.split('');
var uuid = [], rnd = Math.random, r;
uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';
uuid[14] = '4'; // version 4
for (var i = 0; i < 36; i++)
{
if (!uuid[i])
{
r = 0 | rnd()*16;
uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r & 0xf];
}
}
return uuid.join('');
}
以下是生成的 UUID 示例:
682db637-0f31-4847-9cdf-25ba9613a75c
97d19478-3ab2-4aa1-b8cc-a1c3540f54aa
2eed04c9-2692-456d-a0fd-51012f947136
简单的 JavaScript 模块作为此问题中最佳答案的组合。
var crypto = window.crypto || window.msCrypto || null; // IE11 fix
var Guid = Guid || (function() {
var EMPTY = '00000000-0000-0000-0000-000000000000';
var _padLeft = function(paddingString, width, replacementChar) {
return paddingString.length >= width ? paddingString : _padLeft(replacementChar + paddingString, width, replacementChar || ' ');
};
var _s4 = function(number) {
var hexadecimalResult = number.toString(16);
return _padLeft(hexadecimalResult, 4, '0');
};
var _cryptoGuid = function() {
var buffer = new window.Uint16Array(8);
crypto.getRandomValues(buffer);
return [_s4(buffer[0]) + _s4(buffer[1]), _s4(buffer[2]), _s4(buffer[3]), _s4(buffer[4]), _s4(buffer[5]) + _s4(buffer[6]) + _s4(buffer[7])].join('-');
};
var _guid = function() {
var currentDateMilliseconds = new Date().getTime();
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(currentChar) {
var randomChar = (currentDateMilliseconds + Math.random() * 16) % 16 | 0;
currentDateMilliseconds = Math.floor(currentDateMilliseconds / 16);
return (currentChar === 'x' ? randomChar : (randomChar & 0x7 | 0x8)).toString(16);
});
};
var create = function() {
var hasCrypto = crypto != 'undefined' && crypto !== null,
hasRandomValues = typeof(window.crypto.getRandomValues) != 'undefined';
return (hasCrypto && hasRandomValues) ? _cryptoGuid() : _guid();
};
return {
newGuid: create,
empty: EMPTY
};
})();
// DEMO: Create and show GUID
console.log('1. New Guid: ' + Guid.newGuid());
// DEMO: Show empty GUID
console.log('2. Empty Guid: ' + Guid.empty);
用法:
Guid.newGuid()
“c6c2d12f-d76b-5739-e551-07e6de5b0807”
Guid.empty
“00000000-0000-0000-0000-000000000000”
下面的版本是对broofa 答案的改编,但更新为包含一个“真正的”随机函数,该函数在可用的情况下使用加密库,并且 Alea() 函数作为后备。
Math.log2 = Math.log2 || function(n){ return Math.log(n) / Math.log(2); }
Math.trueRandom = (function() {
var crypt = window.crypto || window.msCrypto;
if (crypt && crypt.getRandomValues) {
// If we have a crypto library, use it
var random = function(min, max) {
var rval = 0;
var range = max - min;
if (range < 2) {
return min;
}
var bits_needed = Math.ceil(Math.log2(range));
if (bits_needed > 53) {
throw new Exception("We cannot generate numbers larger than 53 bits.");
}
var bytes_needed = Math.ceil(bits_needed / 8);
var mask = Math.pow(2, bits_needed) - 1;
// 7776 -> (2^13 = 8192) -1 == 8191 or 0x00001111 11111111
// Create byte array and fill with N random numbers
var byteArray = new Uint8Array(bytes_needed);
crypt.getRandomValues(byteArray);
var p = (bytes_needed - 1) * 8;
for(var i = 0; i < bytes_needed; i++ ) {
rval += byteArray[i] * Math.pow(2, p);
p -= 8;
}
// Use & to apply the mask and reduce the number of recursive lookups
rval = rval & mask;
if (rval >= range) {
// Integer out of acceptable range
return random(min, max);
}
// Return an integer that falls within the range
return min + rval;
}
return function() {
var r = random(0, 1000000000) / 1000000000;
return r;
};
} else {
// From https://web.archive.org/web/20120502223108/http://baagoe.com/en/RandomMusings/javascript/
// Johannes Baagøe <baagoe@baagoe.com>, 2010
function Mash() {
var n = 0xefc8249d;
var mash = function(data) {
data = data.toString();
for (var i = 0; i < data.length; i++) {
n += data.charCodeAt(i);
var h = 0.02519603282416938 * n;
n = h >>> 0;
h -= n;
h *= n;
n = h >>> 0;
h -= n;
n += h * 0x100000000; // 2^32
}
return (n >>> 0) * 2.3283064365386963e-10; // 2^-32
};
mash.version = 'Mash 0.9';
return mash;
}
// From http://baagoe.com/en/RandomMusings/javascript/
function Alea() {
return (function(args) {
// Johannes Baagøe <baagoe@baagoe.com>, 2010
var s0 = 0;
var s1 = 0;
var s2 = 0;
var c = 1;
if (args.length == 0) {
args = [+new Date()];
}
var mash = Mash();
s0 = mash(' ');
s1 = mash(' ');
s2 = mash(' ');
for (var i = 0; i < args.length; i++) {
s0 -= mash(args[i]);
if (s0 < 0) {
s0 += 1;
}
s1 -= mash(args[i]);
if (s1 < 0) {
s1 += 1;
}
s2 -= mash(args[i]);
if (s2 < 0) {
s2 += 1;
}
}
mash = null;
var random = function() {
var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32
s0 = s1;
s1 = s2;
return s2 = t - (c = t | 0);
};
random.uint32 = function() {
return random() * 0x100000000; // 2^32
};
random.fract53 = function() {
return random() +
(random() * 0x200000 | 0) * 1.1102230246251565e-16; // 2^-53
};
random.version = 'Alea 0.9';
random.args = args;
return random;
}(Array.prototype.slice.call(arguments)));
};
return Alea();
}
}());
Math.guid = function() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
var r = Math.trueRandom() * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
};
GitHub 上的 JavaScript 项目 - https://github.com/LiosK/UUID.js
UUID.js 符合 RFC 标准的 JavaScript 的 UUID 生成器。
请参阅 RFC 4122 http://www.ietf.org/rfc/rfc4122.txt。
功能 生成符合 RFC 4122 的 UUID。
版本 4 UUID(来自随机数的 UUID)和版本 1 UUID(基于时间的 UUID)可用。
UUID 对象允许对 UUID 进行多种访问,包括对 UUID 字段的访问。
JavaScript 的低时间戳分辨率由随机数补偿。
// RFC 4122
//
// A UUID is 128 bits long
//
// String representation is five fields of 4, 2, 2, 2, and 6 bytes.
// Fields represented as lowercase, zero-filled, hexadecimal strings, and
// are separated by dash characters
//
// A version 4 UUID is generated by setting all but six bits to randomly
// chosen values
var uuid = [
Math.random().toString(16).slice(2, 10),
Math.random().toString(16).slice(2, 6),
// Set the four most significant bits (bits 12 through 15) of the
// time_hi_and_version field to the 4-bit version number from Section
// 4.1.3
(Math.random() * .0625 /* 0x.1 */ + .25 /* 0x.4 */).toString(16).slice(2, 6),
// Set the two most significant bits (bits 6 and 7) of the
// clock_seq_hi_and_reserved to zero and one, respectively
(Math.random() * .25 /* 0x.4 */ + .5 /* 0x.8 */).toString(16).slice(2, 6),
Math.random().toString(16).slice(2, 14)].join('-');
对于那些想要一个符合RFC 4122版本 4 并考虑速度的解决方案的人(很少调用 Math.random()):
var rand = Math.random;
function UUID() {
var nbr, randStr = "";
do {
randStr += (nbr = rand()).toString(16).substr(3, 6);
} while (randStr.length < 30);
return (
randStr.substr(0, 8) + "-" +
randStr.substr(8, 4) + "-4" +
randStr.substr(12, 3) + "-" +
((nbr*4|0)+8).toString(16) + // [89ab]
randStr.substr(15, 3) + "-" +
randStr.substr(18, 12)
);
}
console.log( UUID() );
上述函数应该在速度和随机性之间取得不错的平衡。
我想了解broofa 的回答,所以我对其进行了扩展并添加了评论:
var uuid = function () {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(
/[xy]/g,
function (match) {
/*
* Create a random nibble. The two clever bits of this code:
*
* - Bitwise operations will truncate floating point numbers
* - For a bitwise OR of any x, x | 0 = x
*
* So:
*
* Math.random * 16
*
* creates a random floating point number
* between 0 (inclusive) and 16 (exclusive) and
*
* | 0
*
* truncates the floating point number into an integer.
*/
var randomNibble = Math.random() * 16 | 0;
/*
* Resolves the variant field. If the variant field (delineated
* as y in the initial string) is matched, the nibble must
* match the mask (where x is a do-not-care bit):
*
* 10xx
*
* This is achieved by performing the following operations in
* sequence (where x is an intermediate result):
*
* - x & 0x3, which is equivalent to x % 3
* - x | 0x8, which is equivalent to x + 8
*
* This results in a nibble between 8 inclusive and 11 exclusive,
* (or 1000 and 1011 in binary), all of which satisfy the variant
* field mask above.
*/
var nibble = (match == 'y') ?
(randomNibble & 0x3 | 0x8) :
randomNibble;
/*
* Ensure the nibble integer is encoded as base 16 (hexadecimal).
*/
return nibble.toString(16);
}
);
};
我在这里调整了我自己的 UUID/GUID 生成器,并添加了一些附加功能。
我正在使用以下 Kybos随机数生成器,使其在密码学上更加可靠。
下面是我的脚本,其中排除了 baagoe.com 的 Mash 和 Kybos 方法。
//UUID/Guid Generator
// use: UUID.create() or UUID.createSequential()
// convenience: UUID.empty, UUID.tryParse(string)
(function(w){
// From http://baagoe.com/en/RandomMusings/javascript/
// Johannes Baagøe <baagoe@baagoe.com>, 2010
//function Mash() {...};
// From http://baagoe.com/en/RandomMusings/javascript/
//function Kybos() {...};
var rnd = Kybos();
//UUID/GUID Implementation from http://frugalcoder.us/post/2012/01/13/javascript-guid-uuid-generator.aspx
var UUID = {
"empty": "00000000-0000-0000-0000-000000000000"
,"parse": function(input) {
var ret = input.toString().trim().toLowerCase().replace(/^[\s\r\n]+|[\{\}]|[\s\r\n]+$/g, "");
if ((/[a-f0-9]{8}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{4}\-[a-f0-9]{12}/).test(ret))
return ret;
else
throw new Error("Unable to parse UUID");
}
,"createSequential": function() {
var ret = new Date().valueOf().toString(16).replace("-","")
for (;ret.length < 12; ret = "0" + ret);
ret = ret.substr(ret.length-12,12); //only least significant part
for (;ret.length < 32;ret += Math.floor(rnd() * 0xffffffff).toString(16));
return [ret.substr(0,8), ret.substr(8,4), "4" + ret.substr(12,3), "89AB"[Math.floor(Math.random()*4)] + ret.substr(16,3), ret.substr(20,12)].join("-");
}
,"create": function() {
var ret = "";
for (;ret.length < 32;ret += Math.floor(rnd() * 0xffffffff).toString(16));
return [ret.substr(0,8), ret.substr(8,4), "4" + ret.substr(12,3), "89AB"[Math.floor(Math.random()*4)] + ret.substr(16,3), ret.substr(20,12)].join("-");
}
,"random": function() {
return rnd();
}
,"tryParse": function(input) {
try {
return UUID.parse(input);
} catch(ex) {
return UUID.empty;
}
}
};
UUID["new"] = UUID.create;
w.UUID = w.Guid = UUID;
}(window || this));
ES6 示例
const guid=()=> {
const s4=()=> Math.floor((1 + Math.random()) * 0x10000).toString(16).substring(1);
return `${s4() + s4()}-${s4()}-${s4()}-${s4()}-${s4() + s4() + s4()}`;
}
使用 Blob 的一条线解决方案。
window.URL.createObjectURL(new Blob([])).substring(31);
末尾的值 (31) 取决于 URL 的长度。
编辑:
正如rinogo所建议的,一个更紧凑和通用的解决方案:
window.URL.createObjectURL(new Blob([])).substr(-36);
更好的方法:
function(
a, b // Placeholders
){
for( // Loop :)
b = a = ''; // b - result , a - numeric variable
a++ < 36; //
b += a*51&52 // If "a" is not 9 or 14 or 19 or 24
? // return a random number or 4
(
a^15 // If "a" is not 15,
? // generate a random number from 0 to 15
8^Math.random() *
(a^20 ? 16 : 4) // unless "a" is 20, in which case a random number from 8 to 11,
:
4 // otherwise 4
).toString(16)
:
'-' // In other cases, (if "a" is 9,14,19,24) insert "-"
);
return b
}
最小化:
function(a,b){for(b=a='';a++<36;b+=a*51&52?(a^15?8^Math.random()*(a^20?16:4):4).toString(16):'-');return b}
如果您只需要一个没有特定格式的随机 128 位字符串,您可以使用:
function uuid() {
return crypto.getRandomValues(new Uint32Array(4)).join('-');
}
这将返回类似2350143528-4164020887-938913176-2513998651
.
我找不到任何使用单个 16 字节TypedArray
和 a 的答案,因此我认为以下根据 RFCDataView
生成版本 4 UUID 的解决方案将在这里独立存在:
export const uuid4 = () => {
const ho = (n, p) => n.toString(16).padStart(p, 0); /// Return the hexadecimal text representation of number `n`, padded with zeroes to be of length `p`
const data = crypto.getRandomValues(new Uint8Array(16)); /// Fill the buffer with random data
data[6] = (data[6] & 0xf) | 0x40; /// Patch the 6th byte to reflect a version 4 UUID
data[8] = (data[8] & 0x3f) | 0x80; /// Patch the 8th byte to reflect a variant 1 UUID (version 4 UUIDs are)
const view = new DataView(data.buffer); /// Create a view backed by a 16-byte buffer
return `${ho(view.getUint32(0), 8)}-${ho(view.getUint16(4), 4)}-${ho(view.getUint16(6), 4)}-${ho(view.getUint16(8), 4)}-${ho(view.getUint32(10), 8)}${ho(view.getUint16(14), 4)}`; /// Compile the canonical textual form from the array data
};
我更喜欢它,因为它只依赖于标准 ECMAScript 平台可用的函数,在可能的情况下——这只是一个过程。
在撰写本文时,getRandomValues
还没有为crypto
Node.js 中的对象实现一些东西。randomBytes
但是,它具有可以替代使用的等效功能。
本机URL.createObjectURL
正在生成一个 UUID。您可以利用这一点。
function uuid() {
const url = URL.createObjectURL(new Blob())
const [id] = url.toString().split('/').reverse()
URL.revokeObjectURL(url)
return id
}
SP.Guid.newGuid
如果您的环境是 SharePoint,则有一个称为( MSDN 链接的实用程序函数,它创建一个新的 GUID。此函数位于 sp.init.js 文件中。如果您重写此函数(以从其他私有函数中删除一些其他依赖项),它看起来像这样:
var newGuid = function () {
var result = '';
var hexcodes = "0123456789abcdef".split("");
for (var index = 0; index < 32; index++) {
var value = Math.floor(Math.random() * 16);
switch (index) {
case 8:
result += '-';
break;
case 12:
value = 4;
result += '-';
break;
case 16:
value = value & 3 | 8;
result += '-';
break;
case 20:
result += '-';
break;
}
result += hexcodes[value];
}
return result;
};
以下是crypto.getRandomValues(a)
在支持的浏览器(Internet Explorer 11+、iOS 7+、Firefox 21+、Chrome 和 Android Chrome)上使用的简单代码。
它避免使用Math.random()
,因为这可能会导致冲突(例如Muxa在真实情况下生成的 4000 个 UUID 发生 20 次冲突)。
function uuid() {
function randomDigit() {
if (crypto && crypto.getRandomValues) {
var rands = new Uint8Array(1);
crypto.getRandomValues(rands);
return (rands[0] % 16).toString(16);
} else {
return ((Math.random() * 16) | 0).toString(16);
}
}
var crypto = window.crypto || window.msCrypto;
return 'xxxxxxxx-xxxx-4xxx-8xxx-xxxxxxxxxxxx'.replace(/x/g, randomDigit);
}
笔记:
只是另一个更具可读性的变体,只有两个突变。
function uuid4()
{
function hex (s, b)
{
return s +
(b >>> 4 ).toString (16) + // high nibble
(b & 0b1111).toString (16); // low nibble
}
let r = crypto.getRandomValues (new Uint8Array (16));
r[6] = r[6] >>> 4 | 0b01000000; // Set type 4: 0100
r[8] = r[8] >>> 3 | 0b10000000; // Set variant: 100
return r.slice ( 0, 4).reduce (hex, '' ) +
r.slice ( 4, 6).reduce (hex, '-') +
r.slice ( 6, 8).reduce (hex, '-') +
r.slice ( 8, 10).reduce (hex, '-') +
r.slice (10, 16).reduce (hex, '-');
}
这是基于日期的,并添加了一个随机后缀以“确保”唯一性。
它适用于 CSS 标识符,总是返回类似的东西,并且很容易破解:
uid-139410573297741
var getUniqueId = function (prefix) {
var d = new Date().getTime();
d += (parseInt(Math.random() * 100)).toString();
if (undefined === prefix) {
prefix = 'uid-';
}
d = prefix + d;
return d;
};
好的,使用uuid包及其对版本 1、3、4 和 5 UUID的支持,执行以下操作:
yarn add uuid
进而:
const uuidv1 = require('uuid/v1');
uuidv1(); // ⇨ '45745c60-7b1a-11e8-9c9c-2d42b21b1a3e'
您也可以使用完全指定的选项来执行此操作:
const v1options = {
node: [0x01, 0x23, 0x45, 0x67, 0x89, 0xab],
clockseq: 0x1234,
msecs: new Date('2011-11-01').getTime(),
nsecs: 5678
};
uuidv1(v1options); // ⇨ '710b962e-041c-11e1-9234-0123456789ab'
有关更多信息,请访问此处的 npm 页面。
新增:v15.6.0、v14.17.0 有一个内置的crypto.randomUUID()函数。
import * as crypto from "crypto";
const uuid = crypto.randomUUID();
在浏览器中,crypto.randomUUID()
目前在 Chromium 92+ 和 Firefox 95+ 中受支持。
受broofa回答的启发,我对此有自己的看法:
这是使用crypto.getRandomValues
.
function uuidv4() {
const a = crypto.getRandomValues(new Uint16Array(8));
let i = 0;
return '00-0-4-1-000'.replace(/[^-]/g,
s => (a[i++] + s * 0x10000 >> s).toString(16).padStart(4, '0')
);
}
console.log(uuidv4());
Math.random
这是使用几乎相同原理的更快版本:
function uuidv4() {
return '00-0-4-1-000'.replace(/[^-]/g,
s => ((Math.random() + ~~s) * 0x10000 >> s).toString(16).padStart(4, '0')
);
}
console.log(uuidv4());
重要的是使用由多个贡献者维护的经过良好测试的代码,而不是为此鞭打自己的东西。
这是您可能想要最稳定的代码而不是在 X 浏览器中运行的最短的聪明版本的地方之一,但没有考虑到 Y 的特质,这通常会导致非常难以-调查错误,而不是仅针对某些用户随机显示。我个人在https://github.com/aurigadl/uuid-js使用 uuid-js,它启用了Bower,因此我可以轻松获取更新。
执行此操作的最简单功能:
function createGuid(){
let S4 = () => Math.floor((1+Math.random())*0x10000).toString(16).substring(1);
let guid = `${S4()}${S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`;
return guid.toLowerCase();
}
我正在使用以下功能:
function NewGuid()
{
var sGuid = "";
for (var i=0; i<32; i++)
{
sGuid += Math.floor(Math.random()*0xF).toString(0xF);
}
return sGuid;
}
生成唯一标识的一个简单解决方案是使用时间令牌并向其添加随机数。我更喜欢在它前面加上“uuid-”。
下面的函数将生成一个随机字符串类型:uuid-14d93eb1b9b4533e6。不需要生成 32 个字符的随机字符串。在这种情况下,一个 16 个字符的随机字符串足以在 JavaScript 中提供唯一的 UUID。
var createUUID = function() {
return "uuid-" + ((new Date).getTime().toString(16) + Math.floor(1E7*Math.random()).toString(16));
}
broofa 从 2017-06-28 更新的 TypeScript 版本,基于crypto
API:
function genUUID() {
// Reference: https://stackoverflow.com/a/2117523/709884
return ("10000000-1000-4000-8000-100000000000").replace(/[018]/g, s => {
const c = Number.parseInt(s, 10)
return (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
})
}
原因:
+
betweennumber[]
和number
无效string
to的转换number
必须是明确的这也适用于 Node.js,如果你替换let buffer = new Uint8Array(); crypto.getRandomValues
为let buffer = crypto.randomBytes(16)
它应该在性能上击败大多数正则表达式解决方案。
const hex = '0123456789ABCDEF'
let generateToken = function() {
let buffer = new Uint8Array(16)
crypto.getRandomValues(buffer)
buffer[6] = 0x40 | (buffer[6] & 0xF)
buffer[8] = 0x80 | (buffer[8] & 0xF)
let segments = []
for (let i = 0; i < 16; ++i) {
segments.push(hex[(buffer[i] >> 4 & 0xF)])
segments.push(hex[(buffer[i] >> 0 & 0xF)])
if (i == 3 || i == 5 || i == 7 || i == 9) {
segments.push('-')
}
}
return segments.join('')
}
for (let i = 0; i < 100; ++i) {
console.log(generateToken())
}
性能图表(每个人都喜欢):jsbench
这里有很多正确的答案,但遗憾的是,包含的代码示例非常神秘且难以理解。这就是我创建版本 4(随机)UUID 的方式。
请注意,以下代码使用二进制文字来提高可读性,因此需要 ECMAScript 6。
function uuid4() {
let array = new Uint8Array(16)
crypto.randomFillSync(array)
// Manipulate the 9th byte
array[8] &= 0b00111111 // Clear the first two bits
array[8] |= 0b10000000 // Set the first two bits to 10
// Manipulate the 7th byte
array[6] &= 0b00001111 // Clear the first four bits
array[6] |= 0b01000000 // Set the first four bits to 0100
const pattern = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
let idx = 0
return pattern.replace(
/XX/g,
() => array[idx++].toString(16).padStart(2, "0"), // padStart ensures a leading zero, if needed
)
}
只有第二行不同。
function uuid4() {
let array = new Uint8Array(16)
crypto.getRandomValues(array)
// Manipulate the 9th byte
array[8] &= 0b00111111 // Clear the first two bits
array[8] |= 0b10000000 // Set the first two bits to 10
// Manipulate the 7th byte
array[6] &= 0b00001111 // Clear the first four bits
array[6] |= 0b01000000 // Set the first four bits to 0100
const pattern = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX"
let idx = 0
return pattern.replace(
/XX/g,
() => array[idx++].toString(16).padStart(2, "0"), // padStart ensures a leading zero, if needed
)
}
最后,相应的测试(Jasmine)。
describe(".uuid4()", function() {
it("returns a UUIDv4 string", function() {
const uuidPattern = "XXXXXXXX-XXXX-4XXX-YXXX-XXXXXXXXXXXX"
const uuidPatternRx = new RegExp(uuidPattern.
replaceAll("X", "[0-9a-f]").
replaceAll("Y", "[89ab]"))
for (let attempt = 0; attempt < 1000; attempt++) {
let retval = uuid4()
expect(retval.length).toEqual(36)
expect(retval).toMatch(uuidPatternRx)
}
})
})
UUID 版本 4 的一个很好的解释在这里:生成符合 RFC 4122 的 UUID。
此外,还有很多第三方软件包。但是,只要您只是基本需求,我不推荐它们。真的,没有什么可赢的,也没有什么可输的。作者可能会追求最微小的性能,“修复”不应该修复的东西,而在安全性方面,这是一个冒险的想法。同样,它们可能会引入其他错误或不兼容性。仔细更新需要时间。
以防万一有人通过 Google 寻找小型实用程序库,ShortId满足此问题的所有要求。它允许指定允许的字符和长度,并保证不连续、不重复的字符串。
为了让这更像是一个真正的答案,该库的核心使用以下逻辑来生成其短 ID:
function encode(lookup, number) {
var loopCounter = 0;
var done;
var str = '';
while (!done) {
str = str + lookup( ( (number >> (4 * loopCounter)) & 0x0f ) | randomByte() );
done = number < (Math.pow(16, loopCounter + 1 ) );
loopCounter++;
}
return str;
}
/* Generates the short id */
function generate() {
var str = '';
var seconds = Math.floor((Date.now() - REDUCE_TIME) * 0.001);
if (seconds === previousSeconds) {
counter++;
} else {
counter = 0;
previousSeconds = seconds;
}
str = str + encode(alphabet.lookup, version);
str = str + encode(alphabet.lookup, clusterWorkerId);
if (counter > 0) {
str = str + encode(alphabet.lookup, counter);
}
str = str + encode(alphabet.lookup, seconds);
return str;
}
我没有对此进行编辑以仅反映此方法的最基本部分,因此上述代码包含库中的一些附加逻辑。如果您对它所做的一切感到好奇,请查看源代码:https ://github.com/dylang/shortid/tree/master/lib
在这里你可以找到一个生成 UUID的非常小的函数。
最终版本之一是:
function b(
a // Placeholder
){
var cryptoObj = window.crypto || window.msCrypto; // For Internet Explorer 11
return a // If the placeholder was passed, return
? ( // a random number from 0 to 15
a ^ // unless b is 8,
cryptoObj.getRandomValues(new Uint8Array(1))[0] // in which case
% 16 // a random number from
>> a/4 // 8 to 11
).toString(16) // in hexadecimal
: ( // or otherwise a concatenated string:
[1e7] + // 10000000 +
-1e3 + // -1000 +
-4e3 + // -4000 +
-8e3 + // -80000000 +
-1e11 // -100000000000,
).replace( // Replacing
/[018]/g, // zeroes, ones, and eights with
b // random hex digits
)
}
基于broofa 的工作,我通过将时间戳添加到math.random()
:
function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
var r = parseFloat('0.' + Math.random().toString().replace('0.', '') + new Date().getTime()) * 16 | 0,
v = c == 'x' ? r : (r & 0x3 | 0x8);
return v.toString(16);
});
}
UUID 目前有一个添加到标准库的提案,可以在这里支持ECMAScript 提案:JavaScript 标准库 UUID
该提案包括具有以下 UUID:
// We're not yet certain as to how the API will be accessed (whether it's in the global, or a
// future built-in module), and this will be part of the investigative process as we continue
// working on the proposal.
uuid(); // "52e6953d-edbe-4953-be2e-65ed3836b2f0"
此实现遵循与此处找到的 V4 随机 UUID 生成相同的布局:https ://www.npmjs.com/package/uuid
const uuidv4 = require('uuid/v4');
uuidv4(); // ⇨ '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'
我认为值得注意的是,通过在标准库中进行正式实现可以节省多少带宽。该提案的作者还指出:
12 kB uuid模块每月从 npm 下载 > 62,000,000 次(2019 年 6 月);使其在标准库中可用最终会在全球范围内节省 TB 的带宽。如果我们继续使用标准库来满足用户需求,例如 uuid,那么节省的带宽就会增加。
我已经建立了这里提到的所有内容,以产生两倍于所有环境(包括节点)的可移植性,并从 Math.random() 升级到加密强度随机性。你可能不认为 UUID 需要加密强度,但这意味着发生冲突的可能性更小,这就是 UUID 的全部意义所在。
function random() {
const
fourBytesOn = 0xffffffff, // 4 bytes, all 32 bits on: 4294967295
c = typeof crypto === "object"
? crypto // Node.js or most browsers
: typeof msCrypto === "object" // Stinky non-standard Internet Explorer
? msCrypto // eslint-disable-line no-undef
: null; // What old or bad environment are we running in?
return c
? c.randomBytes
? parseInt(c.randomBytes(4).toString("hex"), 16) / (fourBytesOn + 1) - Number.EPSILON // Node.js
: c.getRandomValues(new Uint32Array(1))[0] / (fourBytesOn + 1) - Number.EPSILON // Browsers
: Math.random();
}
function uuidV4() { // eslint-disable-line complexity
// If possible, generate a single random value, 128 bits (16 bytes)
// in length. In an environment where that is not possible, generate
// and make use of four 32-bit (4-byte) random values.
// Use crypto-grade randomness when available, else Math.random()
const
c = typeof crypto === "object"
? crypto // Node.js or most browsers
: typeof msCrypto === "object" // Stinky non-standard Internet Explorer
? msCrypto // eslint-disable-line no-undef
: null; // What old or bad environment are we running in?
let
byteArray = c
? c.randomBytes
? c.randomBytes(16) // Node.js
: c.getRandomValues(new Uint8Array(16)) // Browsers
: null,
uuid = [ ];
/* eslint-disable no-bitwise */
if ( ! byteArray) { // No support for generating 16 random bytes
// in one shot -- this will be slower
const
int = [
random() * 0xffffffff | 0,
random() * 0xffffffff | 0,
random() * 0xffffffff | 0,
random() * 0xffffffff | 0
];
byteArray = [ ];
for (let i = 0; i < 256; i++) {
byteArray[i] = int[i < 4 ? 0 : i < 8 ? 1 : i < 12 ? 2 : 3] >> i % 4 * 8 & 0xff;
}
}
byteArray[6] = byteArray[6] & 0x0f | 0x40; // Always 4, per RFC, indicating the version
byteArray[8] = byteArray[8] & 0x3f | 0x80; // Constrained to [89ab], per RFC for version 4
for (let i = 0; i < 16; ++i) {
uuid[i] = (byteArray[i] < 16 ? "0" : "") + byteArray[i].toString(16);
}
uuid =
uuid[ 0] + uuid[ 1] + uuid[ 2] + uuid[ 3] + "-" +
uuid[ 4] + uuid[ 5] + "-" +
uuid[ 6] + uuid[ 7] + "-" +
uuid[ 8] + uuid[ 9] + "-" +
uuid[10] + uuid[11] + uuid[12] + uuid[13] + uuid[14] + uuid[15];
return uuid;
/* eslint-enable no-bitwise */
}
这是一个从字符串生成静态 UUID 的函数,如果没有提供字符串,则生成随机 UUID:
function stringToUUID (str)
{
if (str === undefined || !str.length)
str = "" + Math.random() * new Date().getTime() + Math.random();
let c = 0,
r = "";
for (let i = 0; i < str.length; i++)
c = (c + (str.charCodeAt(i) * (i + 1) - 1)) & 0xfffffffffffff;
str = str.substr(str.length / 2) + c.toString(16) + str.substr(0, str.length / 2);
for(let i = 0, p = c + str.length; i < 32; i++)
{
if (i == 8 || i == 12 || i == 16 || i == 20)
r += "-";
c = p = (str[(i ** i + p + 1) % str.length]).charCodeAt(0) + p + i;
if (i == 12)
c = (c % 5) + 1; //1-5
else if (i == 16)
c = (c % 4) + 8; //8-B
else
c %= 16; //0-F
r += c.toString(16);
}
return r;
}
console.log("Random :", stringToUUID());
console.log("Static [1234]:", stringToUUID("1234")); //29c2c73b-52de-4344-9cf6-e6da61cb8656
console.log("Static [test]:", stringToUUID("test")); //e39092c6-1dbb-3ce0-ad3a-2a41db98778c
对于我的用例,我需要保证在全球范围内唯一的 id 生成;没有例外。我为这个问题苦苦挣扎了一段时间,并想出了一个名为TUID(真正唯一 ID)的解决方案。它生成一个 id,前 32 个字符是系统生成的,其余数字表示自纪元以来的毫秒数。在我需要在客户端 JavaScript 代码中生成 id 的情况下,它运行良好。
这是一个工作示例。它生成一个 32 位的唯一UUID。
function generateUUID() {
var d = new Date();
var k = d.getTime();
var str = k.toString(16).slice(1)
var UUID = 'xxxx-xxxx-4xxx-yxxx-xzx'.replace(/[xy]/g, function (c)
{
var r = Math.random() * 16 | 0;
v = c == 'x' ? r : (r & 3 | 8);
return v.toString(16);
});
var newString = UUID.replace(/[z]/, str)
return newString;
}
var x = generateUUID()
console.log(x, x.length)
对于那些在 Windows 上使用 JavaScript(例如,Windows Script Host (WSH)、CScript和HTA)的人。可以使用ActiveX。具体来说,Scriptlet.Typelib
对象:
WScript.Echo((new ActiveXObject("Scriptlet.TypeLib")).Guid)
请注意,此答案仅适用于我列出的技术。它不适用于任何浏览器,甚至 Microsoft Edge!因此,您的里程将随此答案而变化。
我们可以使用 replace 和 crypto.getRandomValues 来获得如下输出:
xxxxxxxx-xxxx-4xxx-xxxx-xxxxxxxxxxxx
如果我们正在寻找一个 opti 解决方案,我们必须用crypto.getRandomValues(new Uint8Array(1))[0]
数组(32)替换。
const uuidv4 = () =>
([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
);
console.log(uuidv4());
要获取此代码:
function uuidv4() {
let bytes = window.crypto.getRandomValues(new Uint8Array(32));
const randomBytes = () => (bytes = bytes.slice(1)) && bytes[0];
return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, c =>
(c ^ randomBytes() & 15 >> c / 4).toString(16)
);
}
for (var i = 0; i < 10; i++)
console.log(uuidv4());
我们可以像谷歌分析一样使用 : 来添加时间戳uuidv4() + "." + (+new Date())
。
以下uuid
实现提供了一个不同的ES6 2020 解决方案,使用BigInt
并专注于“uuid
设计模式的用例意图”;特别适用于索引数据库 primaryKey
场景,在这些场景中统一时间排序和排序是有价值的。
因此,请注意这篇文章有 30 多个答案,这里是...
这篇文章有:
- 带有独立es6的“TL;DR”
code
部分class Xuid
- 关于 es6提供的代码的用例和动机 讨论 部分。
class Xuid
class Xuid
DR解决方案uuid
下面的代码是从我编写和拥有的 Smallscript 的EdgeS网络客户端库中提取的,并在此处提供,免费获得 MIT 许可。EdgeS Web 客户端工具集发布后,将提供GitHub版本。
使用示例:
评估:
console.log(Xuid.v4New)
发出:{1eb4a659-8bdc-4ce0-c002-b1d505d38ea8}
class Xuid {
//@ edges.sm.st, ess.dev: MIT license Smallscript/David Simmons 2020
//! Can't use `static const field = const` xbrowser (thus, const's duped)
static get v4New() {
const ns7Now = this.ns7Now, xnode48 = this.xnode48; let clock_seq13
// monotonic `clock_seq` guarantee (13-bits/time-quantum)
if(ns7Now <= this.ns7Now_prevSeq && this.ns7Now_prevSeq)
clock_seq13 = ((this.ns7Now_prevSeq += 1n) - ns7Now) & 0b1_1111_1111_1111n
else
clock_seq13 = 0n, this.ns7Now_prevSeq = ns7Now
const time60 = ((ns7Now << 4n) & 0xFFFF_FFFF_FFFF_0000n) |
(ns7Now & 0x0000_0000_0000_0FFFn),
v4 = 0x1_00000000_0000_0000_0000_000000000000n |
(time60 << 64n) | (0x00000000_0000_4000_0000_000000000000n) | // M: V4
(0b110n << 61n) | (clock_seq13 << 48n) | // N: Variant-2 time-seq collation
xnode48, s = v4.toString(16)//.substr(1)
return `{${s.substr(1,8)}-${s.substr(9,4)}-${s.substr(13,4)}-${
s.substr(17,4)}-${s.substr(21,12)}}`
}
static get xnode48()/*:<BigInt#48>*/{
if(this.xnode48_) return this.xnode48_
let clockSeqNode; if(typeof URL !== 'undefined' && URL.createObjectURL) {
const url = URL.createObjectURL(new Blob())
const id = (url.toString().split('/').reverse()[0]).split('-')
URL.revokeObjectURL(url)
clockSeqNode = BigInt('0x'+id[3]+id[4])
}
else {
const a4 = this.a4; this.getRandomValues(this.a4);
clockSeqNode = (BigInt(a4[2]) << 32n) | BigInt(a4[3])
}
// simulate the 48-bit node-id and 13-bit clock-seq
// to combine with 3-bit uuid-variant
return this.xnode48_ = clockSeqNode & 0xFFFF_FFFF_FFFFn;
}
static get jdNow()/*:<double#ns7>*/{
// return 2440587.5+Date.now()/864e5 // <- Date-quantum-ms form (7ns form below)
return this.jdFromNs7(this.ns7Now)
}
static get ns7Now()/*:<BigInt#60>*/{
if(typeof performance !== 'undefined' && performance.now)
Reflect.defineProperty(this, 'ns7Now',
Reflect.getOwnPropertyDescriptor(this,'ns7Now_performance'))
else
Reflect.defineProperty(this, 'ns7Now',
Reflect.getOwnPropertyDescriptor(this, 'ns7Now_Date'))
return this.ns7Now
}
static get ns7Now_Date()/*:<BigInt#60>*/{
// const epoch1582Ns7_bias = 0x1b2_1dd2_1381_4000 // V1 1582 Oct 15
// const epoch1601Ns7_bias = 0x19d_b1de_d53e_8000n // FILETIME base
const epoch1970Ns7 = BigInt(Date.now() * 1000_0.0)
return epoch1970Ns7 + 0x1b2_1dd2_1381_4000n
}
static get ns7Now_performance()/*:<BigInt#60>*/{
const epochPgNs7 = BigInt(performance.now()*/*15*/1000_0.0|/*17*/0)
if(!this.epoch1970PgNs7) // performance.timing.navigationStart
this.epoch1970PgNs7 = this.ns7Now_Date - epochPgNs7
return epochPgNs7 + this.epoch1970PgNs7
}
static dateFromJd(jd) {return new Date((jd - 2440587.5) * 864e5)}
static dateFromNs7(ns7) {
return new Date(Number(ns7 - 0x1b2_1dd2_1381_4000n) / 1000_0.0)}
static jdFromNs7(ns7) { // atomic-clock leap-seconds (ignored)
return 2440587.5 + (Number(ns7 - 0x1b2_1dd2_1381_4000n) / 864e9)
}
static ns7FromJd(jd) {
return BigInt((jd - 2440587.5) * 864e9) + 0x1b2_1dd2_1381_4000n
}
static getRandomValues(va/*:<Uint32Array>*/) {
if(typeof crypto !== 'undefined' && crypto.getRandomValues)
crypto.getRandomValues(va)
else for(let i = 0, n = va.length; i < n; i += 1)
va[i] = Math.random() * 0x1_0000_0000 >>> 0
}
static get a4() {return this.a4_ || (this.a4_ = new Uint32Array(4))}
static ntohl(v)/*:<BigInt>*/{
let r = '0x', sign = 1n, s = BigInt(v).toString(16)
if(s[0] == '-') s = s.substr(1), sign = -1n
for(let i = s.length; i > 0; i -= 2)
r += (i == 1) ? ('0' + s[i-1]) : s[i-2] + s[i-1]
return sign*BigInt(r)
}
static ntohl32(v)/*:<Number>*/{return Number(this.ntohl(v))}
}
虽然 v4uuid
定义了一个基本随机的uuid
,但希望有一个uuid
可以支持一些附加特性的实现。
uuid
快速有效地创造新价值(使用BigInt
)80 loc
可读的class
带注释的独立代码uuid
一个time
_context
time
然后context
(使用uuid
Variant-2)time
JavaScript
微秒时钟精度EdgeS
ESS
特别适合与 SQLite 等工具一起使用的数据库。
es6 class
设计来简化名义工作的可扩展性以扩展它以提供其他uuid
变体time
和相关
的 eswc库 API。
ntohl
用于 endian 方便重新排序BigInt
字符串表示的 APIXuid
大括号引用的标量字符串格式支持guid
, uuid
, 和uid
( git
, fossil
, SqLite
repo-id)
表示的地方FILETIME
,等等。
如:
{1eb4a659-8bdc-4ce0-c002-b1d505d38ea8}
object stores
提供了一个理想的解决方案,其中使用 auuid
变得可取primaryKey
。
uuid
time
put
更新efs
service-worker
以及cloud-server
同步和复制动作虽然很简洁,但希望这足以说明现在;试试看。
并且,请随时发表评论、提交反馈或建议。
当在GitHub 上作为EdgeS Web 客户端eswc
库的一部分发布时
,indexedDb使用模式将作为其设计意图的示例,包括解决
indexedDb和相关PWA和场景的效率和可用性。efs
sync
replicate
uuid
秒/秒const start = Xuid.ns7Now
for(let i = 100000; i; i -=1)
Xuid.v4New
const end = Xuid.ns7Now
console.log(`Delta 7ns: ${(end-start)/100000n}`)
结果: 16..20 => ~2 微秒 => 500,000 uuid
s/sec 的值
这只是一个概念,当然可以在很多方面进行改进,但并不像我想象的那么慢。
通常,此代码包含以毫秒为单位的十六进制编码时间戳(通过一些黑客攻击,它会给出 12 位数字,因此该代码即使在 2527-06-24 之后也可以工作,但在 5138-11-16 之后就不行),这意味着它是可排序的。这不是随机的,它使用最后 12 位的MAC 地址。第 13 个字母硬编码为 1,以保持可排序。
之后,接下来的 6 位数字来自半随机字符串,其中第一个数字来自该毫秒生成的记录数,其他数字是随机生成的。该 6 位数字部分包含一个破折号和硬编码字母“a”,以保持记录可排序。
我知道这可以缩短,性能也有所提高,但我对结果很满意(MAC 地址除外)。
currentNanoseconds = () => {
return nodeMode ? process.hrtime.bigint() : BigInt(Date.now() * 1000000);
}
nodeFindMacAddress = () => {
// Extract MAC address
const interfaces = require('os').networkInterfaces();
let result = null;
for (index in interfaces) {
let entry = interfaces[index];
entry.forEach(item => {
if (item.mac !== '00:00:00:00:00:00') {
result = '-' + item.mac.replace(/:/g, '');
}
});
}
return result;
}
const nodeMode = typeof(process) !== 'undefined';
let macAddress = nodeMode ? nodeFindMacAddress() : '-a52e99ef5efc';
let startTime = currentNanoseconds();
let uuids = []; // Array for storing generated UUIDs, useful for testing
let currentTime = null; // Holds the last value of Date.now(), used as a base for generating the UUID
let timePart = null; // Part of the UUID generated from Date.now()
let counter = 0; // Used for counting records created at certain millisecond
let lastTime = null; // Used for resetting the record counter
const limit = 1000000;
for (let testCounter = 0; testCounter < limit; testCounter++) {
let uuid = testMe();
if (nodeMode || testCounter <= 50) {
uuids.push(uuid);
}
}
const timePassed = Number(currentNanoseconds() - startTime);
if (nodeMode) {
const fs = require('fs');
fs.writeFileSync('temp.txt', JSON.stringify(uuids).replace(/,/g, ',\n'));
} else {
console.log(uuids);
}
console.log({
operationsPerSecond: (1000 * limit / timePassed).toString() + 'm',
nanosecondsPerCycle: timePassed / limit,
milliSecondsPassed: timePassed / 1000000,
microSecondsPassed: timePassed / 1000,
nanosecondsPassed: timePassed
});
function testMe() {
currentTime = Date.now();
let uuid = null; // Function result
if (currentTime !== lastTime) {
// Added a 9 before timestamp, so that the hex-encoded timestamp is 12 digits long. Currently, it is 11 digits long, and it will be until 2527-06-24
// console.log(Date.parse("2527-06-24").toString(16).length)
// Code will stop working on 5138-11-17, because the timestamp will be 15 digits long, and the code only handles up to 14 digit timestamps
// console.log((Date.parse("5138-11-17")).toString().length)
timePart = parseInt(('99999999999999' + currentTime).substr(-14)).toString(16);
timePart = timePart.substr(0, 8) + '-' + timePart.substr(8, 4) + '-1';
counter = 0;
}
randomPart = ('000000' + Math.floor(10 * (counter + Math.random()))).slice(-6);
randomPart = randomPart.substr(0, 3) + '-a' + randomPart.substr(3, 3);
uuid = timePart + randomPart + macAddress;
counter++;
lastTime = currentTime;
return uuid;
}
此产品从 az,0-9 返回 5 组 8 位数字,其中大部分是随机的,但包含一天中的时间,并且有一个随机递增的计数器。您可以指定任何您喜欢的基数(十六进制、十进制、36),默认为每组 8 个随机选择一个基数,在基数 16 到 36 的范围内
function newId(base) {
return[
Math.random,
function (){ return (newId.last ? windowId.last + Math.random() : Math.random() ) },
Math.random,
Date.now,
Math.random
].map(function(fn){
return fn().toString(base||(16+(Math.random()*20))).substr(-8);
}).join('-');
}
var demo = function(base){
document.getElementById('uuid').textContent = newId(base);
}
demo(16);
#uuid { font-family: monospace; font-size: 1.5em; }
<p id="uuid"></p>
<button onclick="demo(16);">Hex (base 16)</button>
<button onclick="demo(36);">Base 36</button>
<button onclick="demo(10);">Decimal (base 10)</button>
<button onclick="demo();">Random base</button>
做同样事情的另一种方法:
function guid() {
var chars = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
var str = "";
for(var i=0; i<36; i++) {
var str = str + ((i == 8 || i == 13 || i == 18 || i == 23) ? "-" : chars[Math.floor(Math.random()*chars.length)]);
};
return str;
}
在任何情况下都不要使用Math.random,因为它会生成非加密的随机数源。
下面使用crypto.getRandomValues的解决方案
function uuidv4() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => {
// tslint:disable-next-line: no-bitwise
const r =
(window.crypto.getRandomValues(new Uint32Array(1))[0] *
Math.pow(2, -32) * 16) |
0;
// tslint:disable-next-line: no-bitwise
const v = c === "x" ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}
此链接可帮助您了解Fortify Scanner引发的不安全随机性。
这是我生成具有非常强的唯一性和快速运行时的有效 UUID v4 的简单方法。
基本思想并不新鲜,但方法不同。我使用date.now()
(在 Node.js 库中,我稍后会指出,我使用纳秒时间戳process.hrtime.bigint()
)中的以毫秒为单位的时间戳,然后10000-90000
在时间戳字符串的末尾添加一个随机的 5 位数字 ( )。
合并字符串后,我只是从数字和一对特殊字符组成一个有效的 UUID,这样我的 UUID 只包含数字和一些非数字字符。请在下面查看:
/*
* uuid-timestamp (emitter)
* UUID v4 based on timestamp
*
* Created by tarkh
* tarkh.com (C) 2020
*/
const uuidEmit = () => {
// Get now time
const n = Date.now();
// Generate random
const r = Math.random();
// Stringify now time and generate additional random number
const s = String(n) + String(~~(r*9e4)+1e4);
// Form UUID and return it
return `${s.slice(0,8)}-${s.slice(8,12)}-4${s.slice(12,15)}-${[8,9,'a','b'][~~(r*3)]}${s.slice(15,18)}-${s.slice(s.length-12)}`;
};
// Generate 5 UUIDs
console.log(`${uuidEmit()}
${uuidEmit()}
${uuidEmit()}
${uuidEmit()}
${uuidEmit()}`);
查看结果,您显然看到 UUID 的第一部分是相同的,然后是随机性。这是因为我将时间戳线性插入到 UUID 中。该代码将每毫秒(Node.js 库中的纳秒)生成一个新的 UUID + 在末尾添加一个随机的 5 位数字,因此我们最终得到非常近似的碰撞概率,大约为每秒千万分之一。如果我们使用 Node.js 库,我们非常近似的碰撞概率会达到每秒 100 亿分之一。
由于我们将时间戳线性插入到 UUID 中,因此我们获得了一个特性(好坏 - 取决于任务) - 能够轻松地从 UUID 中提取此时间戳。这样我们就可以了解 UUID 是什么时候发布的:
/*
* uuid-timestamp (parser)
* UUID v4 based on timestamp
*
* Created by tarkh
* tarkh.com (C) 2020
*/
const uuidParse = (uuid) => {
// Get current timestamp string length
let tl = String(Date.now()).length;
// Strip out timestamp from UUID
let ts = '';
let i = -1;
while(tl--) {
i++;
if(i===8||i===13||i===14||i===18||i===19||i===23) {
tl++;
continue;
}
ts += uuid[i];
}
return Number(ts);
};
// Get the timestamp when UUID was emitted
const time = uuidParse('15970688-7109-4530-8114-887109530114');
// Covert timestamp to date and print it
console.log(new Date(time).toUTCString());
我上面的代码的 NPM 版本作为Node.js 模块提供。这个版本在生成唯一值方面更加强大,因为它使用nanoseconds
系统时间和process.hrtime.bigint()
差异的组合而不是毫秒时间戳。
在我的帖子的最后,我想根据这个主题的一些答案做一些性能测试。当然,我的决定不是最快的,但肯定是占据了最高位置。
实际上,在非 Microsoft 圈子中称为 GUID 或 UUID 只是一个 128 位加密随机数,UUID 版本号 (1-5) 位于固定位置字节。
因此,当您生成一堆 0 到 65535 之间的随机数并对其进行十六进制编码时,如下所示:
function guid()
{
function s4()
{
return Math.floor(Math.random() * 65536).toString(16).padStart(4, '0')
} // End Function s4
return s4() + s4() + '-' + s4() + '-' + "4" + s4().substr(1) + '-' + s4() + '-' + s4() + s4() + s4();
} // End Function guid
你得到一个有效的 GUID,但由于随机实现,它不是加密安全的。
要生成加密安全的 GUID,您需要使用 window.crypto(或用于 Internet Explorer 的 window.msCrypto)。
事情是这样的:
function cryptGuid()
{
var array = new Uint16Array(8);
(window.crypto || window.msCrypto).getRandomValues(array);
var dataView = new DataView(array.buffer);
var parts = [];
for(var i = 0; i < array.length; ++i)
{
// 0&1,2,3,4,5-7 dataView.getUint16(0-7)
if(i>1 && i<6) parts.push("-");
parts.push(dataView.getUint16(i).toString(16).padStart(4, '0'));
}
parts[5] = "4" + parts[5].substr(1);
// console.log(parts);
return parts.join('').toUpperCase();// .toLowerCase();
}
cryptGuid();
另外,您必须决定是否将数字作为小写或大写字符串返回。某些软件需要小写字符(例如,Reporting Service),而其他软件则生成大写字符(SQL Server)。
最酷的方式:
function uuid(){
var u = URL.createObjectURL(new Blob([""]))
URL.revokeObjectURL(u);
return u.split("/").slice(-1)[0]
}
它在 IE2 中可能不快速、不高效或不支持,但它确实很酷
这可能对某人有用...
var d = new Date().valueOf();
var n = d.toString();
var result = '';
var length = 32;
var p = 0;
var chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
for (var i = length; i > 0; --i){
result += ((i & 1) && n.charAt(p) ? '<b>' + n.charAt(p) + '</b>' : chars[Math.floor(Math.random() * chars.length)]);
if(i & 1) p++;
};
function randomHex(length) {
var random_string = '';
if(!length){
length = 1;
}
for(var i=0; i<length; i+=1){
random_string += Math.floor(Math.random() * 15).toString(16);
}
return random_string;
}
function guid() {
return randomHex(8);
}
以下内容不符合 v4,但可以轻松更改为。这只是扩展Uint8Array类型并使用crypto.getRandomValues()生成 UUID 字节值的示例。
class uuid extends Uint8Array {
constructor() {
super(16)
/* Not v4, just some random bytes */
window.crypto.getRandomValues(this)
}
toString() {
let id = new String()
for (let i = 0; i < this.length; i++) {
/* Convert uint8 to hex string */
let hex = this[i].toString(16).toUpperCase()
/* Add zero padding */
while (hex.length < 2) {
hex = String(0).concat(hex)
}
id += hex
/* Add dashes */
if (i == 4 || i == 6 || i == 8 || i == 10 || i == 16) {
id += '-'
}
}
return id
}
}
var guid = createMyGuid();
function createMyGuid()
{
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);
});
}
对于您已经通过使用方法为某些资源创建 URL的URL.createObjectURL
情况,您可能不会比以下操作更快或更短:
const uuid = url => url.substr(-36);
以上将适用于任何兼容的实现createObjectURL
,因为规范明确要求将 UUID 添加到前者返回的 URL 的末尾。所以你可以保证最后 36 个字符是生成的 URL 的 UUID 部分。
需要明确的是,有时——哎呀,也许大多数时候,考虑到所有事情——你想为其他东西生成一个 UUID,而不是为你创建 URL 的资源createObjectURL
。在这些情况下,在某些情况下调用后一种方法new Blob()
绝对会降低性能(并且会泄漏内存,除非您自己使用相应的revokeObjectURL
. 不过,它仍然是一个“单线”。
我不建议您仅将上述方法用于生成 UUID,除非您已经通过获取的 URLcreateObjectURL
或末尾具有 UUID 的东西。
为了完整起见,我只想提及上述变体。
我用这个版本。它既安全又简单。它不是生成格式化的uid,它只是生成你需要的随机字符串。
export function makeId(length) {
let result = '';
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const charactersLength = characters.length;
for (let i = 0; i < length; i++) {
let letterPos = crypto.getRandomValues(new Uint8Array(1))[0] / 255 * charactersLength - 1
result += characters[letterPos]
}
return result;
}
RFC4122
这是一种使用真正随机生成的方法via random.org
。如果获取失败,它会退回到浏览器的内置crypto
库,这应该几乎一样好。最后,如果有问题的用户浏览器不支持它,它会使用Math.random()
.
async function UUID() {
//get 31 random hex characters
return (await (async () => {
let output;
try {
//try from random.org
output = (await (
await fetch('https://www.random.org/integers/?num=31&min=0&max=15&col=31&base=16&format=plain&rnd=new')
).text())
//get rid of whitespace
.replace(/[^0-9a-fA-F]+/g, '')
;
if (output.length != 31)
throw '';
}
catch {
output = '';
try {
//failing that, try getting 16 8-bit digits from crypto
for (let num of crypto.getRandomValues(new Uint8Array(16)))
//interpret as 32 4-bit hex numbers
output += (num >> 4).toString(16) + (num & 15).toString(16);
//we only want 31
output = output.substr(1);
}
catch {
//failing THAT, use Math.random
while (output.length < 31)
output += (0 | Math.random() * 16).toString(16);
}
}
return output;
})())
//split into appropriate sections, and set the 15th character to 4
.replace(/^(.{8})(.{4})(.{3})(.{4})/, '$1-$2-4$3-$4-')
//force character 20 to the correct range
.replace(/(?<=-)[^89abAB](?=[^-]+-[^-]+$)/, (num) => (
(parseInt(num, 16) % 4 + 8).toString(16)
))
;
}
使用简单的 uuid 包即可轻松完成 https://www.npmjs.com/package/uuid
const { v4: uuidv4 } = require('uuid');
uuidv4(); // ⇨ '1b9d6bcd-bbfd-4b2d-9b5d-ab8dfbbd4bed'