为何移除sessionStorage后fields_entity仍能读取到旧值?
问题原因分析:表单重置后切换角色仍显示旧值
问题场景
- 点击「Clear formulary」按钮触发
resetForm(),该函数清空所有输入框值,并移除sessionStorage中存储实时数据的fields_entity项,函数内日志显示该项已为null - 切换
#role下拉框选项时,已清空的输入框会被重新填入之前的旧值 - 在
#role的change事件触发的roleOptions()函数中,调用autosave时能读取到fields_entity的旧值,全程未刷新页面 - 已找到解决方案,仅需理解该行为的根本原因,怀疑与
autosave函数有关
相关代码
<!DOCTYPE html> <html lang="en"> <head> <title>Document</title> </head> <body> <form class="form user"> <h2>Cadastrar <span></span></h2> <label for="role">Choose your role</label> <select id="role" name="role"> <option value="teacher" name="teacher">Teacher</option> <option value="student" name="student">Student</option> <option value="adm" name="adm">Collaborator</option> </select> <label for="name">Full name</label> <input type="text" id="name" name="name"> <label for="email">Email</label> <input type="email" id="email" name="email" novalidate> <label for="password">Password</label> <input type="password" id="password" name="password"> <div class="strength-pwd"></div> <button type="submit">Register</button> <button type="button" onclick="resetForm();"> Clear formulary</button> </form> <script> function autosave(form, id) { let fields = sessionStorage.getItem(`fields_${id}`); fields ? (fields = JSON.parse(fields)) : (fields = {}); Array.from(form.elements).forEach(input => { if (input.tagName === "INPUT" || input.tagName === "SELECT") { input.addEventListener("input", () => { fields[input.name] = input.value; sessionStorage.setItem(`fields_${id}`, JSON.stringify(fields)); }); } }); Object.keys(fields).forEach(key => { const input = document.querySelector(`input[name="${key}"], select[name="${key}"]`); if (input) { input.value = fields[key]; } }); } const form = document.querySelector(".form.user"); const role = document.querySelector("#role"); function roleOptions() { autosave(form, "entity"); //Second check = {name : ..., email...}; let roleValue = role.value; switch (roleValue) { case "student": studentFormulary(); break; case "teacher": teacherFormulary(); break; case "adm": admFormulary(); break; default: studentFormulary(); break; } }; role.addEventListener("change", roleOptions); function resetForm() { form.querySelectorAll("input").forEach(e => { e.value = ""; }); sessionStorage.removeItem("fields_entity"); //First check = null } function studentFormulary() { //... } function teacherFormulary() { //... } function admFormulary() { //... } roleOptions(); </script> </body> </html>
行为原因解析
1. 重复绑定事件监听器引发的闭包陷阱
每次调用autosave(form, "entity")(包括页面初始化、每次切换角色时),都会给所有input/select元素重复绑定新的input事件监听器。
第一次调用autosave时(页面加载完成执行roleOptions()),fields变量捕获了当时从sessionStorage读取的用户输入数据,后续这些旧的监听器会一直持有这个fields对象的引用——哪怕你在resetForm里删除了sessionStorage中的fields_entity项,这些旧监听器里的fields依然是最初的旧数据对象。
2. 事件触发与sessionStorage的读写时序问题
当你点击重置按钮后:
resetForm()清空输入框并删除sessionStorage的fields_entity,此时该项确实为null- 切换角色触发
roleOptions(),首先调用autosave:- 第一步从
sessionStorage读取到fields_entity为null,因此初始化fields为空对象 - 但紧接着,函数又给输入元素绑定了新的
input监听器,同时旧的监听器并未被移除 - 切换下拉框的操作可能触发了旧监听器的执行(部分浏览器中select的change事件会触发绑定的input事件,或者监听器的触发时机存在延迟),旧监听器会把自己持有的旧
fields数据重新写入sessionStorage - 最后
autosave执行遍历赋值逻辑,从刚被旧监听器写回的sessionStorage中读取到旧值,重新填充到输入框里
- 第一步从
3. autosave函数的设计缺陷
函数没有做事件监听器的去重处理,导致每次调用都会叠加新的监听器,而旧监听器持有的闭包变量不会被垃圾回收,始终保留着最初的旧数据引用,这是问题的核心根源。
内容的提问来源于stack exchange,提问作者Nikolas
相关产品推荐
相关产品推荐

