You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在generatePassword中使用checkedCount并解决无限调用栈溢出问题

解决密码生成与安全指示器的无限递归问题

问题

我想在generatePassword函数里用checkedCount变量,当checkedCount为0时,不调用末尾的matrixEffect函数。尝试通过参数传递checkedCountValue实现跨函数使用,但因为updateSafetyIndicator末尾会调用generatePassword,导致两个函数无限互相调用,出现错误:RangeError: Maximum call stack size exceeded。

原因

generatePassword内部调用了updateSafetyIndicator,而updateSafetyIndicator又调用了generatePassword,形成无限递归循环,每次调用都会占用调用栈空间,直到超出浏览器栈大小限制。

解决方案

  1. 拆分函数职责:让generatePassword只负责生成密码和控制矩阵特效调用;updateSafetyIndicator只负责更新安全指示器状态,两个函数不再互相调用。
  2. 封装公共逻辑:把计算checkedCount的代码抽成独立函数,避免重复编写。
  3. 统一触发入口:在生成按钮点击、选项变更、滑块调整等事件中,先计算checkedCount,再分别调用对应函数。

修改后的完整代码

const lengthSlider = document.querySelector(".length-wrapper input"),
options = document.querySelectorAll(".container input"),
generateBtn = document.querySelector(".generate-btn"),
copyBtn = document.querySelector(".copy-btn"),
passwordSafetyIndicator = document.querySelector(".safety-indicator"),
passwordInput = document.querySelector(".display-font");

const characters = {
    lowercase: "abcdefghijkilmnopqrstuvwxyz",
    uppercase: "ABCDEFGHIJKLMNOPQRSTUVWXYZ",
    digits: "0123456789",
    specials: "^!$%&|[](){}:;.,*+-#@<>~"
}

// 封装计算选中项数量的函数
function getCheckedCount() {
    let count = 0;
    options.forEach(option => {
        if(option.checked) count++;
    });
    return count;
}

// 生成密码
function generatePassword(checkedCount) {
    let staticPassword = "",
    randomPassword = "",
    passwordLength = lengthSlider.value;
    
    if(checkedCount === 0) {
        passwordInput.innerText = "Please select first...";
        return;
    }

    options.forEach(option => {
        if(option.checked) {
            staticPassword += characters[option.id];
        }
    });

    for (let i = 0; i < passwordLength; i++) {
        randomPassword += staticPassword[Math.floor(Math.random() * staticPassword.length)];
    }

    passwordInput.innerText = randomPassword;

    // 只有选中项数量大于0时才调用矩阵特效
    if (checkedCount > 0) {
        matrixEffect(randomPassword);
    } 
}

// 矩阵特效
const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
let interval = null;

function matrixEffect(password) {  
    let iteration = 0;
    clearInterval(interval);
    
    interval = setInterval(() => {
      passwordInput.innerText = passwordInput.innerText
        .split("")
        .map((letter, index) => {
          if(index < iteration) {
            return password[index];
          }
          return letters[Math.floor(Math.random() * 26)]
        })
        .join("");
      
      if(iteration >= lengthSlider.value){ 
        clearInterval(interval);
      }
      
      iteration += 1 / 3;
    }, 30);
};

// 更新安全指示器
function updateSafetyIndicator() {
    const checkedCount = getCheckedCount();
    const passwordLength = lengthSlider.value;

    if (checkedCount === 0) {
        passwordSafetyIndicator.removeAttribute("id");
        return;
    }

    // 根据选中数量和长度设置安全等级
    if (checkedCount === 4 && passwordLength >= 10) {
        passwordSafetyIndicator.id = "verystrong";
    } else if (checkedCount === 4 || (checkedCount === 3 && passwordLength >= 10)) {
        passwordSafetyIndicator.id = "strong";
    } else if (checkedCount === 3 || (checkedCount === 2 && passwordLength >= 10)) {
        passwordSafetyIndicator.id = "medium";
    } else if (checkedCount === 2 || (checkedCount === 1 && passwordLength >= 10)) {
        passwordSafetyIndicator.id = "weak";
    } else if (checkedCount === 1) {
        passwordSafetyIndicator.id = "veryweak";
    }
}

// 更新滑块长度
function updateSlider() {
    document.querySelector(".length-wrapper span").innerText = lengthSlider.value;
    // 滑块变更时更新安全指示器
    updateSafetyIndicator();
}

updateSlider();

// 复制密码
function copyPassword(htmlElement) {
    if (!htmlElement) return;

    let password = htmlElement.innerText;
    if(password === "Please select first..." || password === "COPIED") return;

    let inputPassword = document.createElement("input");
    inputPassword.setAttribute("value", password);
    document.body.appendChild(inputPassword);
    inputPassword.select();
    document.execCommand("copy");
    inputPassword.parentNode.removeChild(inputPassword);
    passwordInput.innerText = "COPIED";

    setTimeout(() => {
        passwordInput.innerText = password;
    }, 1300);
}

// 事件监听
copyBtn.onclick = () => copyPassword(passwordInput);
lengthSlider.addEventListener("input", updateSlider);
generateBtn.addEventListener("click", () => {
    const checkedCount = getCheckedCount();
    generatePassword(checkedCount);
    updateSafetyIndicator();
});

// 选项变更时更新安全指示器
options.forEach(option => {
    option.addEventListener("change", updateSafetyIndicator);
});

关键修改点

  • 新增getCheckedCount函数,统一计算选中的选项数量,避免重复代码
  • 移除generatePassword和updateSafetyIndicator之间的互相调用,各自只负责单一职责
  • 在生成按钮点击事件中,先获取checkedCount,再依次调用generatePassword和updateSafetyIndicator
  • 给选项添加change事件,选中状态变更时自动更新安全指示器
  • 优化安全指示器的判断逻辑,合并重复条件

内容的提问来源于stack exchange,提问作者flexbeatz

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 08:07:03