¿Cómo creo un GUID/UUID?

Resuelto Jason Cohen asked hace 15 años • 74 respuestas

¿Cómo creo GUID (identificadores únicos a nivel mundial) en JavaScript? El GUID/UUID debe tener al menos 32 caracteres y debe permanecer en el rango ASCII para evitar problemas al pasarlos.

No estoy seguro de qué rutinas están disponibles en todos los navegadores, qué tan "aleatorio" y sembrado es el generador de números aleatorios incorporado, etc.

Jason Cohen avatar Sep 20 '08 03:09 Jason Cohen
Aceptado

[Editado el 5 de marzo de 2023 para reflejar las mejores prácticas más recientes para producir UUID compatibles con RFC4122]

crypto.randomUUID()ahora es estándar en todos los navegadores modernos y tiempos de ejecución JS. Sin embargo, debido a que las nuevas API del navegador están restringidas a contextos seguros , este método solo está disponible para páginas servidas localmente ( localhosto 127.0.0.1) o a través de HTTPS.

Para los lectores interesados ​​en otras versiones de UUID, generando UUID en plataformas heredadas o en contextos no seguros, existe el uuidmódulo . Está bien probado y respaldado.

A falta de lo anterior, existe este método (basado en la respuesta original a esta pregunta):

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());
Expandir fragmento

Nota: Se desaconseja encarecidamente el uso de cualquier generador de UUID que dependa de élMath.random() (incluidos los fragmentos que aparecen en versiones anteriores de esta respuesta) por las razones que se explican mejor aquí . TL;DR: las soluciones basadas en Math.random()no ofrecen buenas garantías de unicidad.

broofa avatar Jan 22 '2010 13:01 broofa

Los UUID (Universally Unique IDentifier), también conocidos como GUID (Globally Unique IDentifier), según RFC 4122 , son identificadores diseñados para proporcionar ciertas garantías de unicidad.

Si bien es posible implementar UUID compatibles con RFC en unas pocas líneas de código JavaScript (por ejemplo, consulte la respuesta de @broofa a continuación), existen varios errores comunes:

  • Formato de identificación no válido (los UUID deben tener el formato " xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx", donde x es uno de [0-9, af] M es uno de [1-5] y N es [8, 9, a o b]
  • Uso de una fuente de aleatoriedad de baja calidad (como Math.random)

Por lo tanto, se anima a los desarrolladores que escriben código para entornos de producción a utilizar una implementación rigurosa y bien mantenida, como el módulo uuid .

broofa avatar Sep 19 '2008 20:09 broofa

Realmente me gusta lo limpia que es la respuesta de Broofa , pero es lamentable que las implementaciones deficientesMath.random dejen la posibilidad de colisión.

Aquí hay una solución similar compatible con RFC4122 versión 4 que resuelve ese problema compensando los primeros 13 números hexadecimales por una porción hexadecimal de la marca de tiempo, y una vez agotados, los compensa por una porción hexadecimal de los microsegundos desde la carga de la página. De esa manera, incluso si Math.randomestá en la misma semilla, ambos clientes tendrían que generar el UUID exactamente la misma cantidad de microsegundos desde que se cargó la página (si se admite el tiempo de alto rendimiento) Y exactamente en el mismo milisegundo (o más de 10,000 años después) para obtener el mismo 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>
Expandir fragmento

Aquí tienes un violín para probar.


Fragmento modernizado para ES6

Mostrar fragmento de código

Briguy37 avatar Jan 10 '2012 19:01 Briguy37

La respuesta de broofa es bastante hábil, de hecho: impresionantemente inteligente, realmente... compatible con RFC4122, algo legible y compacta. ¡Impresionante!

Pero si observa esa expresión regular, esas muchas replace()devoluciones de llamada toString()y Math.random()llamadas a funciones (donde solo usa cuatro bits del resultado y desperdicia el resto), puede comenzar a preguntarse sobre el rendimiento. De hecho, joelpt incluso decidió descartar un RFC para la velocidad del GUID genérico con generateQuickGUID.

Pero, ¿podemos conseguir velocidad y cumplimiento de RFC? ¡Yo digo si! ¿Podemos mantener la legibilidad? Bueno... En realidad no, pero es fácil si lo sigues.

Pero primero, mis resultados, en comparación con broofa guid(la respuesta aceptada) y el que no cumple con 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.

Entonces, en mi sexta iteración de optimizaciones, superé la respuesta más popular por más de 12 veces , la respuesta aceptada por más de 9 veces y la respuesta rápida que no cumple por 2 o 3 veces . Y sigo cumpliendo con RFC 4122.

¿Interesado en cómo? Puse la fuente completa en http://jsfiddle.net/jcward/7hyaC/3/ y en https://jsben.ch/xczxS

Para una explicación, comencemos con el código de 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())
Expandir fragmento

Por lo tanto, lo reemplaza xcon cualquier dígito hexadecimal aleatorio, ycon datos aleatorios (excepto forzar que los dos bits superiores cumplan con 10la especificación RFC), y la expresión regular no coincide con los caracteres -o 4, por lo que no tiene que lidiar con ellos. Muy, muy hábil.

Lo primero que debe saber es que las llamadas a funciones son costosas, al igual que las expresiones regulares (aunque solo usa 1, tiene 32 devoluciones de llamada, una para cada coincidencia, y en cada una de las 32 devoluciones de llamada llama a Math.random() y v. a Cadena (16)).

El primer paso hacia el rendimiento es eliminar RegEx y sus funciones de devolución de llamada y utilizar un bucle simple en su lugar. Esto significa que tenemos que lidiar con los personajes -y 4mientras que broofa no lo hizo. Además, tenga en cuenta que podemos usar la indexación de String Array para mantener su elegante arquitectura de plantilla de String:

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

Básicamente, la misma lógica interna, excepto que verificamos -o 4y usamos un bucle while (en lugar de replace()devoluciones de llamada) ¡nos brinda una mejora de casi 3 veces!

El siguiente paso es pequeño en el escritorio, pero marca una diferencia decente en el móvil. Hagamos menos llamadas a Math.random() y utilicemos todos esos bits aleatorios en lugar de desechar el 87% de ellos con un búfer aleatorio que se desplaza en cada iteración. También saquemos la definición de plantilla del ciclo, por si acaso ayuda:

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

Esto nos ahorra entre un 10 y un 30% según la plataforma. Nada mal. Pero el siguiente gran paso elimina por completo las llamadas a la función toString con un clásico de optimización: la tabla de búsqueda. Una tabla de búsqueda simple de 16 elementos realizará el trabajo de toString(16) en mucho menos tiempo:

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

La siguiente optimización es otro clásico. Dado que solo manejamos cuatro bits de salida en cada iteración del bucle, reduzcamos el número de bucles a la mitad y procesemos ocho bits en cada iteración. Esto es complicado ya que todavía tenemos que manejar las posiciones de bits que cumplen con RFC, pero no es demasiado difícil. Luego tenemos que crear una tabla de búsqueda más grande (16x16 o 256) para almacenar 0x00 - 0xFF, y la construimos solo una vez, fuera de la función 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())
Expandir fragmento

Probé un e6() que procesa 16 bits a la vez, todavía usando la LUT de 256 elementos , y mostró los rendimientos decrecientes de la optimización. Aunque tenía menos iteraciones, la lógica interna se complicaba por el mayor procesamiento y funcionaba igual en el escritorio, y solo ~10% más rápido en el móvil.

La última técnica de optimización a aplicar es desenrollar el bucle. Dado que estamos repitiendo un número fijo de veces, técnicamente podemos escribir todo esto a mano. Intenté esto una vez con una única variable aleatoria, rque seguí reasignando, y el rendimiento se desplomó. Pero con cuatro variables asignadas datos aleatorios desde el principio, luego usando la tabla de búsqueda y aplicando los bits RFC adecuados, esta versión los elimina a todos:

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

Modualizado: http://jcward.com/UUID.js -UUID.generate()

Lo curioso es que generar 16 bytes de datos aleatorios es la parte fácil. Todo el truco consiste en expresarlo en formato de cadena que cumpla con RFC, y se logra mejor con 16 bytes de datos aleatorios, un bucle desenrollado y una tabla de búsqueda.

Espero que mi lógica sea correcta: es muy fácil cometer un error en este tipo de trabajo tedioso. Pero los resultados me parecen buenos. ¡Espero que hayas disfrutado de este loco viaje a través de la optimización del código!

Tenga en cuenta: mi objetivo principal era mostrar y enseñar posibles estrategias de optimización. Otras respuestas cubren temas importantes como colisiones y números verdaderamente aleatorios, que son importantes para generar buenos UUID.

Jeff Ward avatar Feb 23 '2014 01:02 Jeff Ward

Usar:

let uniqueId = Date.now().toString(36) + Math.random().toString(36).substring(2);

Mostrar fragmento de código

Si los ID se generan con una diferencia de más de 1 milisegundo, son 100 % únicos.

Si se generan dos ID en intervalos más cortos, y suponiendo que el método aleatorio es verdaderamente aleatorio, esto generaría ID que tienen un 99,99999999999999% de probabilidad de ser globalmente únicos (colisión en 1 de 10^15).

Puede aumentar este número agregando más dígitos, pero para generar identificaciones 100% únicas necesitará usar un contador global.

Si necesita compatibilidad RFC, este formato se considerará un GUID de versión 4 válido:

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

Mostrar fragmento de código

El código anterior sigue la intención, pero no la letra del RFC. Entre otras discrepancias, le faltan unos pocos dígitos aleatorios. (Agregue más dígitos aleatorios si lo necesita) La ventaja es que esto es realmente rápido :) Puede probar la validez de su GUID aquí

Simon Rigét avatar May 19 '2017 20:05 Simon Rigét