如何在generatePassword中使用checkedCount并解决无限调用栈溢出问题
解决密码生成与安全指示器的无限递归问题
问题
我想在generatePassword函数里用checkedCount变量,当checkedCount为0时,不调用末尾的matrixEffect函数。尝试通过参数传递checkedCountValue实现跨函数使用,但因为updateSafetyIndicator末尾会调用generatePassword,导致两个函数无限互相调用,出现错误:RangeError: Maximum call stack size exceeded。
原因
generatePassword内部调用了updateSafetyIndicator,而updateSafetyIndicator又调用了generatePassword,形成无限递归循环,每次调用都会占用调用栈空间,直到超出浏览器栈大小限制。
解决方案
- 拆分函数职责:让
generatePassword只负责生成密码和控制矩阵特效调用;updateSafetyIndicator只负责更新安全指示器状态,两个函数不再互相调用。 - 封装公共逻辑:把计算
checkedCount的代码抽成独立函数,避免重复编写。 - 统一触发入口:在生成按钮点击、选项变更、滑块调整等事件中,先计算
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
相关产品推荐
相关产品推荐

