优化 JavaScript – 密码生成器(速度提高 2.15 倍)

16,757次阅读
没有评论

共计 7495 个字符,预计需要花费 19 分钟才能阅读完成。

我在 Github explore 上搜索时发现了一个密码生成器(omgopass)(https://github.com/omgovich/omgopass),据说它比其他替代品要快得多。比 600 倍快 password-generator(https://www.npmjs.com/package/password-generator)

这是基准测试 omgopass 显示:

原始 omgopass 基准

看到这个后,我记得几周前我做了一个密码生成器,并且没有执行任何基准测试,所以我决定用其他库测试我的方法。

令我惊讶的是,它表现得相当好,在相同的基准测试中获得第二名,如上所示。即使不尝试也很好。

使用我的 pass 生成器进行基准测试 (passGenny)(https://github.com/nombrekeff/pass-genny):

通过 passGenny 进行基准测试

注意事项

该基准测试并不能反映库的质量或开发人员的技能,以真正确保应该进行更多的测试和基准测试。

此外,每个库的功能也有所不同,有些是可读的,有些则不可读。有些使用加密进行随机,有些则不使用。

话虽这么说,

让我们让 passGenny 更快

我决定尝试一下,并尝试优化它,让我们看看原始代码:

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 

这个类的作用是,从一组选项中生成密码。它通过将(选项)允许的所有字符组合到一个数组中来实现此目的,然后迭代密码的长度(由选项定义),并从该数组中获取随机字符。

够简单吧?现在,我认为我们可以对此进行一些优化,好吗?

优化 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;
    }
}

请注意我们现在如何返回一个字符串,而不是一个数组。

让我们看看它在基准测试中的表现如何

通过一级优化的 passGenny 进行基准测试

妈的,没想到变化这么大,几乎翻了一倍。

正如您所看到的,在这种特殊情况下,字符串的性能比数组好得多。

可是等等

我想我可以进一步优化它,你可能已经注意到,使用相同的选项,结果_getCharactersForOptions 总是相同的。这意味着我们不需要连接每个密码上的字符串,我们只需要在选项更改时生成它们。

我们可以通过多种方式来解决这个问题,使用记忆化(可能更好)、围绕对象创建代理或我接下来将向您展示的简单方法。

优化 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 
  1. 我们添加, 指示自上次调用_optionsChanged 以来选项是否已更改。_getCharactersForOptions

  2. 我们将最后一个组合字符存储在_combinedCharacters

  3. 我们修改_getCharactersForOptions,这样如果选项没有改变,我们返回最后生成的_combinedCharacters

  4. 我们改变 password +=(password.concat()在我的测试中,它 concat 的表现比 += 更好)

就是这样,让我们看看它是如何做到的:

passGenny 的基准测试具有两个优化级别

如果你问我的话,令人印象深刻的是,我们传球的速度比吉尼快了一倍,以相当大的优势取得了第一。如果我们像 omgovich 那样表述它,它比密码生成器 passGenny(https://www.npmjs.com/package/password-generator)快 2,444 倍

从中可以得到什么?

  • 保持简单可以等同于高性能

  • 如果不需要,不要使用数组

  • 检查是否每次都需要执行操作

  • 如果您需要性能,有时较小的事情会产生最大的差异

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 

性能方面我的可能是最差的(没有测试它,但我认为 setter/getter 的性能不如仅使用方法,我可能是错的),但除了性能之外,我们总是必须考虑 DX,并且从我的从角度来看,在处理类时,getter 和 setter 提供了更好的 DX。我个人更喜欢只使用函数,甚至不去上课。

网友反馈留言 1

我的主要原因主要是用法:

import {Something} from"./Something";

console.log(Something.aValue);

// vs

import {aValue} from "./Something";

console.log(aValue);

我知道,超级利基,但是以前从类中有用的封装现在我从模块中获得:D文章来源地址 https://www.toymoban.com/diary/js/377.html

到此这篇关于优化 JavaScript - 密码生成器(速度提高 2.15 倍)的文章就介绍到这了, 更多相关内容可以在右上角搜索或继续浏览下面的相关文章,希望大家以后多多支持 TOY 模板网!

原文地址:https://www.toymoban.com/diary/js/377.html

如若转载,请注明出处:如若内容造成侵权 / 违法违规 / 事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

    正文完
     0
    Yojack
    版权声明:本篇文章由 Yojack 于1970-01-01发表,共计7495字。
    转载说明:
    1 本网站名称:优杰开发笔记
    2 本站永久网址:https://yojack.cn
    3 本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长进行删除处理。
    4 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
    5 本站所有内容均可转载及分享, 但请注明出处
    6 我们始终尊重原创作者的版权,所有文章在发布时,均尽可能注明出处与作者。
    7 站长邮箱:laylwenl@gmail.com
    评论(没有评论)