在JavaScript中生成随机字符串/字符。

2782
我希望得到一个由随机选取的字符组成的5个字符的字符串,可用字符集为[a-zA-Z0-9]
使用JavaScript,最佳方法是什么?

87
警告:所有答案都不具有“真正的随机”结果!它们只是“伪随机”。在使用随机字符串进行保护或安全时,不要使用它们中的任何一个!!!请尝试使用以下其中一种API:http://www.random.org/ - Ron van der Heijden
22
请注意:HTML5 webcrypto randomness API 提供真正的随机性。 - mikemaccana
6
如果有人在这里寻求生成一个ID或唯一标识符,让你的数据库来完成这项工作,或者使用UUID库。 - Dan
使用JavaScript、Java、Python、Rust和Bash生成随机字符串:https://fe-tool.com/zh-cn/random-password - cwtuan
试试npm库random-ext - undefined
94个回答

3662

我认为这个方法适合你:

function makeid(length) {
    let result = '';
    const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    const charactersLength = characters.length;
    let counter = 0;
    while (counter < length) {
      result += characters.charAt(Math.floor(Math.random() * charactersLength));
      counter += 1;
    }
    return result;
}

console.log(makeid(5));


126
在几乎任何情况下这可能都没有关系:http://www.codinghorror.com/blog/2009/01/the-sad-tragedy-of-micro-optimization-theater.html - Alex Reece
5
可能看起来有些不对,但 floor 是不必要的:for(var text=''; text.length < 5;) text += possible.charAt(Math.random() * possible.length)(说明:这段代码是JavaScript中生成一个随机5位字符串的代码。原文想表达的是使用Math.random()函数生成随机数时,向下取整操作floor是可选的,因为该操作不影响最终结果。) - Collin Anderson
11
@dan_waterworth,实际上,出于某些原因,即使在循环内部使用,+= 经常更快 - http://jsperf.com/join-vs-concatenation - Konrad Borowski
22
“给我看数据。”参见先前的评论,其中包含jsperf链接或http://jsperf.com/sad-tragedy-of-microoptimization或http://www.sitepen.com/blog/2008/05/09/string-performance-an-analysis/等内容。此外,这个问题只涉及到5个字符串拼接。 - Alex Reece
25
请勿在密码加盐过程中使用这个方法。它只是伪随机而不安全。假设您正在使用Node(如果您在客户端进行加盐,我甚至不确定从哪里开始),请改用 crypto - Hydrothermal
显示剩余16条评论

3144

//Can change 7 to 2 for longer results.
let r = (Math.random() + 1).toString(36).substring(7);
console.log("random", r);

注意:以上算法存在以下弱点:
  • 它会因为浮点数的字符串化而生成0到6个字符,因为尾随零在字符串化时被删除。
  • 它深度依赖于用于字符串化浮点数的算法,该算法极其复杂。(请参阅文献"如何准确地打印浮点数"。)
  • Math.random()可能会根据实现产生可预测(看起来像随机但实际上不是随机的)输出。当你需要保证唯一性或不可预测性时,生成的字符串不适合使用。
  • 即使它产生了6个均匀随机、不可预测的字符,在生成约50,000个字符串后,你可以期望看到一个重复项,这是由于生日悖论引起的。(sqrt(36^6) = 46656)

331
Math.random().toString(36).substr(2, 5) 的意思是生成一个长度为5的随机字符串,其中包含字母和数字。使用 .substring(7) 会使字符串长度超过5个字符。无论如何,此代码仍然可以得到全部分数! - dragon
86
在 JavaScript 中,数值类型的 toString 方法可以接受一个可选参数,将该数值转换为指定进制。例如,如果你传入 2,则会将数值转换为二进制表示。和十六进制(基数为 16)类似,三十六进制使用字母来表示大于 9 的数字。通过将随机数转换为三十六进制,你将得到一系列看起来毫无规律的字母和数字。 - Chris Baker
85
看起来很漂亮,但在某些情况下会生成空字符串!如果随机返回0、0.5、0.25、0.125等数值,将导致字符串为空或很短。 - gertas
81
这可以通过(Math.random() + 1).toString(36).substring(7);来避免。 - George Reith
35
@hacklikecrack,这些重复出现是因为substring(7)从base-36字符串的最低有效部分获取数字。toString(36)似乎将随机数转换为16位base-36字符串,但需要的精度是36^16=7.958e24个可能的数字,而Math.random()的精度仅为4.5e15。通过使用.slice(2,5)从最高有效端获取数字可以解决这个问题:5位数字示例 - Luke
显示剩余17条评论

911

Math.random不适合这种情况

服务器端

使用nodecrypto模块 -

var crypto = require("crypto");
var id = crypto.randomBytes(20).toString('hex');

// "bb5dc8842ca31d4603d6aa11448d1654"

生成的字符串长度将是随机字节的两倍; 每个编码为十六进制的字节是2个字符。20个字节将是40个十六进制字符。


客户端

使用浏览器的加密模块 crypto.getRandomValues -

crypto.getRandomValues()方法可以让您获取具有密码学强度的随机值。给定作为参数的数组填充了随机数字(在其密码学含义中为随机)。

// dec2hex :: Integer -> String
// i.e. 0-255 -> '00'-'ff'
function dec2hex (dec) {
  return dec.toString(16).padStart(2, "0")
}

// generateId :: Integer -> String
function generateId (len) {
  var arr = new Uint8Array((len || 40) / 2)
  window.crypto.getRandomValues(arr)
  return Array.from(arr, dec2hex).join('')
}

console.log(generateId())
// "82defcf324571e70b0521d79cce2bf3fffccd69"

console.log(generateId(20))
// "c1a050a4cd1556948d41"

一步一步的控制台示例 -

> var arr = new Uint8Array(4) # make array of 4 bytes (values 0-255)
> arr
Uint8Array(4) [ 0, 0, 0, 0 ]

> window.crypto
Crypto { subtle: SubtleCrypto }

> window.crypto.getRandomValues()
TypeError: Crypto.getRandomValues requires at least 1 argument, but only 0 were passed

> window.crypto.getRandomValues(arr)
Uint8Array(4) [ 235, 229, 94, 228 ]

您可以使用以下方法来支持IE11 -

(window.crypto || window.msCrypto).getRandomValues(arr)

浏览器支持情况请参见https://caniuse.com/#feat=getrandomvalues


客户端(旧版浏览器)

如果您必须支持旧版浏览器,请考虑使用类似于uuid的东西 -

const uuid = require("uuid");
const id = uuid.v4();

// "110ec58a-a0f2-4ac4-8393-c866d813b8d1"

7
没错。虽然UUID适用于为一个物体分配一个ID,但将其作为一串随机字符来使用并不是个好主意(也许还有其他原因)。 - wmassingham
3
第三种方法不需要使用.map()Array.from(arr, dec2hex).join('')Array.from(arr).map(dec2hex).join('')相同。感谢介绍这些功能 :-) - Fred Gandt
7
答案中应该提到选项2也需要使用Node.js才能工作,它不是纯JavaScript。 - Esko
12
虽然更加密码学安全,但这并不满足问题的要求,因为它只输出0-9和a-f(十六进制),而不是0-9、a-z、A-Z。 - qJake
1
@AneesAhmed777,dec2hex提供了一个示例编码器。你可以自行选择如何表示字节。我已经根据你的建议更新了帖子。 - Mulan
显示剩余17条评论

318

简短、易懂、可靠

返回正好5个随机字符,与在此处找到的一些最受欢迎的答案相反。

Math.random().toString(36).slice(2, 7);

25
如果 Math.random().toString(36) 返回的数字少于 5 个字符,那该怎么办? - Michael Litvin
8
这是@Aperçu一个有趣的控诉,我并不是说我发明了这个解决方案,但我已经在我的项目中使用它多年了。这与你提到的评论无关。在SO上最重要的是最有用的信息放在正确的位置,即使它已经存在于其他地方也是如此。幸运的是,至少有51位用户发现了这个答案很有用,不用谢! - Silver Ringvee
6
Math.random()函数可能返回0,但不会返回1。参考链接:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/random - mikep
10
我已经运行了这段代码十亿次,仍然没有得到空字符串:jsfiddle.net/mtp5730r。我认为你很安全,不用担心会得到空字符串。 - MacroMan
25
某个事件的发生统计上的不太可能并不能说明它是安全的。如果有人把这个事件用于任何安全相关的活动,他们就是在赌 Math.random 不返回 0 的概率很小这个不确定性上。希望你永远不必调试这个事件。 - Purefan
显示剩余13条评论

190
这里是对doubletap优秀回答的改进。原版有两个缺点,在此得到解决:
首先,正如其他人所提到的,它有很小的概率产生短字符串甚至为空字符串(如果随机数为0),这可能会破坏您的应用程序。以下是解决方案:
(Math.random().toString(36)+'00000000000000000').slice(2, N+2)

其次,原始代码和上面的解决方案都将字符串大小N限制为16个字符。以下代码将返回任何大小为N的字符串(但请注意,使用N>16不会增加随机性或减少碰撞概率):

Array(N+1).join((Math.random().toString(36)+'00000000000000000').slice(2, 18)).slice(0, N)

解释:

  1. 在区间 [0,1) 中随机选择一个数字,即在 0(包括)和 1(不包括)之间。
  2. 将数字转换为基数为 36 的字符串,即使用数字 0-9 和字母 a-z。
  3. 填充零(解决第一个问题)。
  4. 切掉前导的“0.”前缀和额外的填充零。
  5. 重复字符串,直到至少有 N 个字符(通过使用较短的随机字符串作为分隔符连接空字符串来实现)。
  6. 从字符串中精确地切割出 N 个字符。

进一步思考:

  • 这个解决方案不使用大写字母,但在几乎所有情况下(不是故意的),这并不重要。
  • 原始答案中N = 16时的最大字符串长度是在Chrome中测量的。在Firefox中,它是N = 11。但正如所解释的那样,第二个解决方案是关于支持任何请求的字符串长度,而不是添加随机性,因此并没有太大区别。
  • 所有返回的字符串都有相等的概率被返回,至少在Math.random()返回的结果均匀分布的情况下是如此(无论如何,这不是加密强度的随机性)。
  • 并非所有大小为N的可能字符串都会被返回。在第二个解决方案中,这很明显(因为较小的字符串只是被复制),但在原始答案中也是如此,因为在转换为36进制时,最后几位可能不是原始随机位的一部分。具体来说,如果您查看Math.random().toString(36)的结果,您会注意到最后一个字符不是均匀分布的。同样,在几乎所有情况下这并不重要,但我们从随机字符串的开头而不是结尾切割最终字符串,以便短字符串(例如N = 1)不受影响。

更新:

以下是我想到的几个函数式一行代码。它们与上面的解决方案不同之处在于:
  • 它们使用显式任意字母表(更通用,并适合最初的问题,该问题要求大写和小写字母)。
  • 长度为N的所有字符串具有相等的返回概率(即字符串不包含重复项)。
  • 它们基于map函数,而不是toString(36)技巧,这使它们更直接和易于理解。
假设您选择的字母表是:
var s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

那么这两个是等效的,所以你可以选择其中更符合你直觉的一个:

Array(N).join().split(',').map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');

并且

Array.apply(null, Array(N)).map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('');

编辑:

看起来 qubyteMartijn de Milliano 想到了类似后者的解决方案(赞!),但我不知道。由于它们一眼看上去不够简短,因此无论如何我都会在这里留下它,以防有人真的想要一个单行语句 :-)

此外,在所有解决方案中将 'new Array' 替换为 'Array' 以减少几个字节。


为什么不直接加1呢?(Math.random()+1).toString(36).substring(7); - Mike Doe
使用 Math.random().toString(36).substring(2,7) 可以得到一个预期的结果,这个结果更像是 .substring(2, n+2) - Joseph Rex
Array.apply(null, {length: 5}).map(function() { return s.charAt(Math.floor(Math.random() * s.length)); }).join('') - Muhammad Umer
@EdwardNewell,你的观察是正确的,针对 Firefox 和 N > 11 的情况,解决方案和说明基本相同——具体来说,使用大的 N 值并不会增加任何随机性,但确保了字符串长度的准确性,这是原始解决方案所缺少的。我会更新答案。 - amichair
这篇文章的更新部分很棒,它完全通过了 https://stackoverflow.com/questions/1349404/generate-random-string-characters-in-javascript#comment30312741_8084248 中描述的测试,并且与 hacky 方法一样快。 - w00t
显示剩余3条评论

158

最简洁的解决方案,因为slicesubstring更短。从字符串末尾减去可以避免random函数生成的浮点符号:

Math.random().toString(36).slice(-5);

或者甚至

(+new Date).toString(36).slice(-5);

更新:添加了一种使用btoa方法的方法:

btoa(Math.random()).slice(0, 5);
btoa(+new Date).slice(-7, -2);
btoa(+new Date).substr(-7, 5);

// Using Math.random and Base 36:
console.log(Math.random().toString(36).slice(-5));

// Using new Date and Base 36:
console.log((+new Date).toString(36).slice(-5));

// Using Math.random and Base 64 (btoa):
console.log(btoa(Math.random()).slice(0, 5));

// Using new Date and Base 64 (btoa):
console.log(btoa(+new Date).slice(-7, -2));
console.log(btoa(+new Date).substr(-7, 5));


Math.random().toString(36).slice(-5); - 如果 Math.random() 返回 0.0 会怎样? - x-ray
3
没错。;) 如果 Math.random() 返回 0.5,结果就是 "0.i"。不确定是否还有其他边缘情况。只是想指出这不是问题(从 [a-zA-Z0-9] 中选取 5 个字符)的正确答案。 - x-ray
2
@x-ray,我不会说这是正确的答案,我只是说这是上面@doubletap答案的紧凑版本。我个人使用(+new Date + Math.random())来防止这种情况。无论如何,感谢您的提醒。 - Valentin Podkamennyi
1
为什么完全不使用bota呢?我在其他答案中进行了评论,但是使用此测试用例,您可以看到您只会使用64个字符中的14个字符,这是base64提供的: [...new Set([...Array(100000)].map(()=>btoa(Math.random()).substr(0, 5)).join(""))].sort() - Or Duan
这在大约20次运行后,在两个不同的地方给了我相同的结果。不要使用。 - Desperado
显示剩余2条评论

136

使用es6展开运算符的新版本:

[...Array(30)].map(() => Math.random().toString(36)[2]).join('')

  • 30是任意数字,您可以选择任何令牌长度
  • 36是您可以传递给numeric.toString()的最大基数,这意味着所有数字和小写字母a-z
  • 2用于选择类似于"0.mfbiohx64i"的随机字符串中的第三个索引,我们可以在0.之后取任何索引

你能解释一下吗?特别是为什么你将36传递给toString(),以及为什么选择第三个元素? - vuza
不错,但这个解决方案不包括大写字母[A-Z],只有小写字母和数字[a-z0-9]。 - Nahuel Greco
@NahuelGreco 你可以执行以下代码:[...Array(30)].map(() => Math.random().toString(36)[2]).join('').toUpperCase() - tam.teixeira
@tam.teixeira 我认为不是,原问题要求使用大小写混合,每个字符必须从 [a-zA-Z0-9] 集合中随机选择。这个答案并不满足这个要求。 - Nahuel Greco
Array(30).fill().map(() => Math.random().toString(36).slice(2)).join('')生成一个庞大的随机字符串。 - Hoxz
2
我还会添加一个 || '0',这样如果 Math.random() 返回 0(它永远不会返回 1),它就会回退到 0 而不是 undefined - Juan Campa

119

这样类似的代码应该可以正常工作。

function randomString(len, charSet) {
    charSet = charSet || 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
    var randomString = '';
    for (var i = 0; i < len; i++) {
        var randomPoz = Math.floor(Math.random() * charSet.length);
        randomString += charSet.substring(randomPoz,randomPoz+1);
    }
    return randomString;
}

使用默认字符集[a-zA-Z0-9]进行调用,或者发送您自己的字符集:

var randomValue = randomString(5);

var randomValue = randomString(5, 'PICKCHARSFROMTHISSET');

这里可以找到一个示例的变体:http://www.mediacollege.com/internet/javascript/number/random.html - David
2
最好直接在while循环中递减“len”。 - drzaus
谢谢,我刚刚发布了这个示例的Coffeescript变体:https://dev59.com/dnM_5IYBdhLWcg3wgja2#26682781 - Dinis Cruz
如果这些将被公开使用,那么您应该删除元音字母。熵会减少一点,但更安全,因为您无法生成可能冒犯人的任何单词。这就是我喜欢它的原因,因为我可以发送自己的字符字符串。干得好。 - Nate Bunney
请注意,在Node服务器上,它非常简单:crypto.randomBytes(3).toString('hex')(例如,3个字节会产生6个字符)。 - Fattie

78

function randomstring(L) {
  var s = '';
  var randomchar = function() {
    var n = Math.floor(Math.random() * 62);
    if (n < 10) return n; //1-10
    if (n < 36) return String.fromCharCode(n + 55); //A-Z
    return String.fromCharCode(n + 61); //a-z
  }
  while (s.length < L) s += randomchar();
  return s;
}
console.log(randomstring(5));


我最喜欢这个。你可以轻松修改以接受额外的参数并仅返回数字、小写或大写字母。我认为超压缩风格并不必要 "while(s.length< L) s+= randomchar();"。 - mastaBlasta
3
while(L--)会完成它。 - vsync
3
最好使用更加健壮的'A'.charCodeAt(0)代替神奇数字55(同样适用于61)。特别是在我的平台上,神奇数字返回的是65。这样代码也更易于自我说明。 - Grumdrig
我在这里创建了一个最小化优化(比您的实现少46个字节)的变体:https://dev59.com/dnM_5IYBdhLWcg3wgja2#52412162 - Waruyama

75

随机字符串生成器(字母数字 | 字母 | 数字)

/**
 * Pseudo-random string generator
 * https://dev59.com/dnM_5IYBdhLWcg3wgja2#27872144
 * Default: return a random alpha-numeric string
 * 
 * @param {Integer} len Desired length
 * @param {String} an Optional (alphanumeric), "a" (alpha), "n" (numeric)
 * @return {String}
 */
function randomString(len, an) {
  an = an && an.toLowerCase();
  var str = "",
    i = 0,
    min = an == "a" ? 10 : 0,
    max = an == "n" ? 10 : 62;
  for (; i++ < len;) {
    var r = Math.random() * (max - min) + min << 0;
    str += String.fromCharCode(r += r > 9 ? r < 36 ? 55 : 61 : 48);
  }
  return str;
}

console.log(randomString(10));      // i.e: "4Z8iNQag9v"
console.log(randomString(10, "a")); // i.e: "aUkZuHNcWw"
console.log(randomString(10, "n")); // i.e: "9055739230"


虽然上述内容对所需的 A/N、A、N 输出使用了额外的检查,但让我们将其简化为基本要素(仅限字母数字),以获得更好的理解:

  • 创建一个函数,接受一个参数(随机字符串结果的所需长度)
  • 创建一个空字符串,如 var str = "";,用于连接随机字符
  • 在循环内创建一个从 0 到 61 的随机索引号(0..9+A..Z+a..z = 62)
  • 创建条件逻辑调整/修复 rand(因为它是 0..61),通过增加某个数字(参见下面的示例)来获取正确的 CharCode 数字和相关字符。
  • 在循环内连接到 str 上一个 String.fromCharCode( incremented rand )

让我们来看一下 ASCII 字符表 范围:

_____0....9______A..........Z______a..........z___________  Character
     | 10 |      |    26    |      |    26    |             Tot = 62 characters
    48....57    65..........90    97..........122           CharCode ranges

Math.floor( Math.random() * 62 )可以得到0..61的范围(我们需要的)。
为了得到正确的charCode范围,让我们修正一下随机数:

      |   rand   | charCode |  (0..61)rand += fix            = charCode ranges |
------+----------+----------+--------------------------------+-----------------+
0..9  |   0..9   |  48..57  |  rand += 48                    =     48..57      |
A..Z  |  10..35  |  65..90  |  rand += 55 /*  90-35 = 55 */  =     65..90      |
a..z  |  36..61  |  97..122 |  rand += 61 /* 122-61 = 61 */  =     97..122     |

上表中的三元条件运算符逻辑:

   rand += rand>9 ? ( rand<36 ? 55 : 61 ) : 48 ;
// rand +=  true  ? (  true   ? 55 else 61 ) else 48 ;

通过上面的解释,这是生成的字母数字片段

function randomString(len) {
  var str = "";                                // String result
  for (var i = 0; i < len; i++) {              // Loop `len` times
    var rand = Math.floor(Math.random() * 62); // random: 0..61
    var charCode = rand += rand > 9 ? (rand < 36 ? 55 : 61) : 48; // Get correct charCode
    str += String.fromCharCode(charCode);      // add Character to str
  }
  return str; // After all loops are done, return the concatenated string
}

console.log(randomString(10)); // i.e: "7GL9F0ne6t"

或者如果您愿意:

const randomString = (n, r='') => {
  while (n--) r += String.fromCharCode((r=Math.random()*62|0, r+=r>9?(r<36?55:61):48));
  return r;
};

console.log(randomString(10))


1
由于某种我无法确定的原因,x.toString(36) --- 正如上面的答案所使用的那样 --- 在比较具有不同 ES 版本的两个应用程序的输出时,不能可靠地生成相同的字符。而这个函数对我来说解决了这个问题,因为它不使用 .toString() - Harley Lang

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