我在 Github explore 上搜索時發(fā)現了一個密碼生成器(omgopass)(https://github.com/omgovich/omgopass),據說它比其他替代品要快得多。比 600 倍快password-generator(https://www.npmjs.com/package/password-generator)。
這是基準測試omgopass顯示:
看到這個后,我記得幾周前我做了一個密碼生成器,并且沒有執(zhí)行任何基準測試,所以我決定用其他庫測試我的方法。
令我驚訝的是,它表現得相當好,在相同的基準測試中獲得第二名,如上所示。即使不嘗試也很好。
使用我的 pass 生成器進行基準測試 ( passGenny)(https://github.com/nombrekeff/pass-genny):
注意事項
該基準測試并不能反映庫的質量或開發(fā)人員的技能,以真正確保應該進行更多的測試和基準測試。
此外,每個庫的功能也有所不同,有些是可讀的,有些則不可讀。有些使用加密進行隨機,有些則不使用。
話雖這么說,
讓我們讓passGenny更快
我決定嘗試一下,并嘗試優(yōu)化它,讓我們看看原始代碼:
class PasswordGenerator { static upperCaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''); static lowerCaseChars = 'abcdefghijklmnopqrstuvwxyz'.split(''); static symbolsChars = '<>[]{}=?()&%$#@!???*_-.:;,'.split(''); static numbersString = '0123456789'.split(''); constructor(options = {}) { this.options = { uppercase: true, lowercase: true, symbols: false, numbers: true, readable: false, length: 12, ...options, }; } updateOptions(newOptions = {}) { this.options = { ...this.options, ...newOptions, }; } random(min = 0, max = 10) { return Math.floor( Math.random() * (max - min) + min ); } _getCharactersForOptions() { const combinedCaracters = []; if (this.options.lowercase) combinedCaracters.push(...PasswordGenerator.lowerCaseChars); if (this.options.uppercase) combinedCaracters.push(...PasswordGenerator.upperCaseChars); if (this.options.symbols) combinedCaracters.push(...PasswordGenerator.symbolsChars); if (this.options.numbers) combinedCaracters.push(...PasswordGenerator.numbersString); return combinedCaracters; } generate() { let combinedCaracters = this._getCharactersForOptions(); let password = ''; for (let c = 0; c < this.options.length; c++) { password += combinedCaracters[this.random(0, combinedCaracters.length)]; } return password; } }
這個類的作用是,從一組選項中生成密碼。它通過將(選項)允許的所有字符組合到一個數組中來實現此目的,然后迭代密碼的長度(由選項定義),并從該數組中獲取隨機字符。
夠簡單吧?現在,我認為我們可以對此進行一些優(yōu)化,好嗎?
優(yōu)化1
好的,我注意到的第一件事是,在 中_getCharactersForOptions,我使用數組來保存有效字符。使用擴展運算符將它們附加到combinedCaracters數組中。
這有點多余,因為我們可以一直使用字符串。連接字符串比組合數組便宜得多。
讓我們看看我們可以改變什么。
首先我們需要改變存儲字符的方式,我們不需要分割它們:
class PasswordGenerator { static upperCaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; static lowerCaseChars = 'abcdefghijklmnopqrstuvwxyz'; static symbolsChars = '<>[]{}=?()&%$#@!???*_-.:;,'; static numbersString = '0123456789'; // ... more code }
太好了,現在我們來修改_getCharactersForOptions方法:
class PasswordGenerator { _getCharactersForOptions() { let combinedCaracters = ''; if (this.options.lowercase) combinedCaracters += PasswordGeneratorFast1.lowerCaseChars; if (this.options.uppercase) combinedCaracters += PasswordGeneratorFast1.upperCaseChars; if (this.options.symbols) combinedCaracters += PasswordGeneratorFast1.symbolsChars; if (this.options.numbers) combinedCaracters += PasswordGeneratorFast1.numbersString; return combinedCaracters; } }
請注意我們現在如何返回一個字符串,而不是一個數組。
讓我們看看它在基準測試中的表現如何
媽的,沒想到變化這么大,幾乎翻了一倍。
正如您所看到的,在這種特殊情況下,字符串的性能比數組好得多。
可是等等
我想我可以進一步優(yōu)化它,你可能已經注意到,使用相同的選項,結果_getCharactersForOptions總是相同的。這意味著我們不需要連接每個密碼上的字符串,我們只需要在選項更改時生成它們。
我們可以通過多種方式來解決這個問題,使用記憶化(可能更好)、圍繞對象創(chuàng)建代理或我接下來將向您展示的簡單方法。
優(yōu)化2
我要做的是,將選項設為私有,并迫使人們使用updateOptions方法更改選項。這將允許我標記選項是否已更改。
讓我們看一下完整的示例,然后我將對其進行分解:
class PasswordGeneratorFast2 { static upperCaseChars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; static lowerCaseChars = 'abcdefghijklmnopqrstuvwxyz'; static symbolsChars = '<>[]{}=?()&%$#@!???*_-.:;,'; static numbersString = '0123456789'; constructor(options = {}, randomFn) { this._options = { uppercase: true, lowercase: true, symbols: false, numbers: true, readable: false, length: 12, ...options, }; this._random = randomFn || mathRandom; this._combinedCharacters = ''; this._optionsChanged = true; this._getCharactersForOptions(); } updateOptions(newOptions = {}) { this._options = { ...this._options, ...newOptions, }; this._optionsChanged = true; } generate() { const combinedCaracters = this._getCharactersForOptions(); const length = combinedCaracters.length; let password = ''; for (let c = 0; c < this._options.length; c++) { password = password.concat(combinedCaracters[this._random(0, length)]); } return password; } _getCharactersForOptions() { // If options have not changed, we can return the previoulsy combined characters if (!this._optionsChanged) return this._combinedCharacters; let combinedCaracters = ''; if (this._options.lowercase) combinedCaracters += PasswordGeneratorFast1.lowerCaseChars; if (this._options.uppercase) combinedCaracters += PasswordGeneratorFast1.upperCaseChars; if (this._options.symbols) combinedCaracters += PasswordGeneratorFast1.symbolsChars; if (this._options.numbers) combinedCaracters += PasswordGeneratorFast1.numbersString; // Update and mark options as not changed this._combinedCharacters = combinedCaracters; this._optionsChanged = false; return this._combinedCharacters; } }
我們添加, 指示自上次調用_optionsChanged以來選項是否已更改。_getCharactersForOptions
我們將最后一個組合字符存儲在_combinedCharacters
我們修改_getCharactersForOptions,這樣如果選項沒有改變,我們返回最后生成的_combinedCharacters
我們改變password +=(password.concat()在我的測試中,它 concat 的表現比 += 更好)
就是這樣,讓我們看看它是如何做到的:
如果你問我的話,令人印象深刻的是,我們傳球的速度比吉尼快了一倍,以相當大的優(yōu)勢取得了第一。如果我們像omgovich那樣表述它,它比密碼生成器passGenny(https://www.npmjs.com/package/password-generator)快 2,444 倍
從中可以得到什么?
保持簡單可以等同于高性能
如果不需要,不要使用數組
檢查是否每次都需要執(zhí)行操作
如果您需要性能,有時較小的事情會產生最大的差異
PD:我不是性能專家,所以我可能會錯過一些重要的事情,如果我錯過了某些內容或誤解了結果,請告訴我。
網友反饋留言1
您還可以進行一些改進(主要針對 DX,而不是針對性能):
使用 使私有成員真正成為私有成員#。
使用 getter 和 setter。
將靜力學移至常量。
的經典類過程this.generate = this.generate.bind(this);,因此人們可以在地圖等內容中使用該函數而不會出現問題。
應用這些建議,代碼看起來像這樣(我添加了 JSDocs 以獲得更好的 DX):
/** * @typedef PasswordGeneratorOptions * @property {number} [length=12] * @property {boolean} [lowercase=true] * @property {boolean} [numbers=true] * @property {typeof mathRandom} [randomFunction=mathRandom] * @property {boolean} [symbols=false] * @property {boolean} [uppercase=true] *//** * @param {number} min * @param {number} max */const mathRandom = (min, max) => Math.floor(Math.random() * (max - min) + min);const uppercaseChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";const lowercaseChars = "abcdefghijklmnopqrstuvwxyz";const symbolsChars = "<>[]{}=?()&%$#@!???*_-.:;,";const numbersString = "0123456789";export class PasswordGeneratorFast { /** @type {PasswordGeneratorOptions} */ #options = { length: 12, lowercase: true, numbers: true, randomFunction: mathRandom, symbols: false, uppercase: true }; #characters = ""; #optionsChanged = true; set options(options) { this.#optionsChanged = true; this.#options = { ...this.#options, ...options }; } get options() { return this.#options; } get characters() { if (this.#optionsChanged) { this.#characters = (this.#options.lowercase ? lowercaseChars : "") + (this.#options.uppercase ? uppercaseChars : "") + (this.#options.symbols ? symbolsChars : "") + (this.#options.numbers ? numbersString : ""); this.#optionsChanged = false; } return this.#characters; } /** @param {PasswordGeneratorOptions} options */ constructor(options = {}) { this.options = options; this.generate = this.generate.bind(this); } generate() { const { characters } = this; const length = characters.length; let password = ""; for (let char = 0; char < this.#options.length; char++) { password = password.concat( characters[this.#options.randomFunction(0, length)] ); } return password; }}
性能方面我的可能是最差的(沒有測試它,但我認為 setter/getter 的性能不如僅使用方法,我可能是錯的),但除了性能之外,我們總是必須考慮 DX,并且從我的從角度來看,在處理類時,getter 和 setter 提供了更好的 DX。我個人更喜歡只使用函數,甚至不去上課。
網友反饋留言1
我的主要原因主要是用法:文章來源:http://www.zghlxwxcb.cn/article/377.html
import { Something } from "./Something"; console.log(Something.aValue); // vs import { aValue } from "./Something"; console.log(aValue);
我知道,超級利基,但是以前從類中有用的封裝現在我從模塊中獲得:D文章來源地址http://www.zghlxwxcb.cn/article/377.html
到此這篇關于優(yōu)化 JavaScript - 密碼生成器(速度提高 2.15 倍)的文章就介紹到這了,更多相關內容可以在右上角搜索或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!