const bufferToHex = (() => { // 16进制字符集,如果需要大写,可以设置成"0123456789ABCDEF" // 写在闭包里是因为这个是个常量,放在内存中可以优化性能,不必每次都重新申明 const BASE = "0123456789abcdef"; // 这个函数会被返回,即bufferToHex return buffer => { // 首先拿到传入缓冲流的长度,创建固定长度的数组。这个数组即为结果数组 // 因为固定长度的数组会先在内存中分配数组,所以性能比动态扩展的更好 const n = buffer.length; const hex = new Array(n); // 对每个buf,把它转成字符串 // 因为只有字符串才能固化,数字类型的在机器中就是2进制,你用十进制的脚本解释器看就是十进制的了 // 使用for,是因为for比数组原型链上的方法性能更好 // 使用下标递减而不是递增,是因为递减性能更好 for (let i = n - 1, buf = buffer[i]; buf; buf = buffer[i -= 1]) { // 现在来看每个buf,其数值应是0-255,标识为二进制就是0baaaabbbb,十进制运算是16a + b // buf >>> 4表示无符号右移4位,那么相当于只取a的部分。即变成0b0000aaaa // buf & 0b1111表示做位与运算,这样相当于只取b的部分。即变成0b0000bbbb // 注:上述说明中的a和b仅相对于占位,并不代表实际数值。即aaaa可能是1111,也可能是1010等等 // 这样就分别做出了2个16进制的数,然后在字符串中的下标就是对应的16进制的字符 // 这样一个buf就翻译完了 // // 当然你可以直接使用buf.toString(16),这样连BASE也不需要了 // 但这样写,是因为使用下标和位运算更快,并且内存使用更少 hex[i] = BASE[buf >>> 4] + BASE[buf & 0b1111]; } return hex; }; })(); const buffer = new Uint8Array(16); crypto.getRandomValues(buffer); console.log(buffer, bufferToHex(buffer));