← HTML 表单:从控件到提交与校验 / 重置表单:dirty value flag 与默认值 待审核 8 / 24
reset · dirty value flag

重置表单:dirty value flag 与默认值

每个 resettable 控件其实存着两个值:你在 HTML 里写下的默认值<input value> 属性、<textarea> 的子文本、checkbox 的 checked 属性),以及用户改出来的当前值。规范用一个dirty value flag把两者钉在一起:控件没被碰过时 flag 为 false,此时改 value 属性会同步反映到显示;一旦用户输入过、或脚本写过 .value,flag 翻成 true,再改 value 属性不再影响显示form.reset()type=reset 按钮做的,正是把每个控件还原回默认值、并清掉这个 flag。这页把「当前值 / 默认值」并排摊开,把 dirty flag 当场点出来。

1 · 当前值 vs 默认值:reset 把右边搬回左边

下面这张 <form> 给每个控件都写了默认值(text、textarea、一个默认勾选的 checkbox、一个默认选中第二项的 <select>)。右侧实时并排打出每个控件的当前值(.value / .checked)与默认值(.defaultValue / .defaultChecked)。随便改:当前值跟着变,默认值纹丝不动。点 reset(form 内 type=reset),全部还原——注意 reset 事件可取消,这里没拦它,让原生还原真实发生。

默认值的来源,各控件不一样<input value="x">value 属性即 defaultValue<textarea>默认文本</textarea>子文本节点是它的 defaultValue(textarea 没有 value 属性);checkbox / radio 的 checked 属性即 defaultChecked<select> 的默认选中项 = 带 selected 属性的那个 <option>。reset 算法依次把每个控件还原到这些默认值,并清掉它们各自的 dirty flag。

2 · dirty value flag:同一行 setAttribute('value', …),为何有时生效有时不生效

这是「服务端改了 value 属性,前端有时看见、有时看不见」的根因。下面一个 input,两个按钮都执行 input.setAttribute('value', 'SERVER'),区别只在之前有没有动过这个 input:左边直接改属性(input 还干净,dirty=false);右边先替你打一个字(模拟用户输入,把 dirty 翻成 true)再改属性。readout 把dirty 状态 / value 属性 / 当前 .value三者并排打出来,自己对照显示值变没变。

什么会把 dirty flag 翻成 true。三类操作:用户在控件里输入 / 编辑、脚本给 .value(IDL setter)赋值、以及部分会改值的 UA 行为。一旦为 true,后续改 value 属性(setAttribute / 改 HTML)只更新 defaultValue,不再覆盖当前显示值。form.reset() 会把它清回 false——这也是为什么 reset 后,再改 value 属性又能生效了。

3 · 同一段代码里,谁读默认值、谁读当前值

IDL 属性(.value / .checked)读的是当前值,content attribute(getAttribute / defaultValue / defaultChecked)读的是默认值——同一个 <input> 上两套读法各自稳定,互不干扰。

<input id="u" value="initial" />

u.value                 // "initial"(current value,IDL)
u.defaultValue          // "initial"(= value 属性)
u.getAttribute('value') // "initial"(content attribute)

// 用户在框里把它改成 "edited" 之后:
u.value                 // "edited"  ← 当前值跟着变
u.defaultValue          // "initial" ← 默认值不变
u.getAttribute('value') // "initial" ← 同上

// dirty value flag 的分水岭:
u.setAttribute('value', 'SERVER')
// flag=false(没碰过)→ 显示值变成 "SERVER"
// flag=true (输入过 / 写过 .value)→ 只改 defaultValue,显示值不动

u.form.reset()          // 还原回 defaultValue,并把 dirty flag 清回 false
读法 读到的是 随用户输入变吗
input.value 当前值 (IDL, current value)
input.defaultValue 默认值(= value 属性) 不变
input.getAttribute('value') 默认值 (content attribute) 不变
checkbox.checked 当前 checkedness
checkbox.defaultChecked 默认 checkedness(= checked 属性) 不变
textarea.value 当前文本
textarea.defaultValue 默认文本(= 子文本节点) 不变
select.value 当前选中 option 的 value

这正是 SSR / 注水的隐蔽陷阱。服务端渲染时往 <input value="..."> 写值,只要客户端 JS 在用户输入之前读到、且没人改过 .value,属性值就是显示值,一切正常;但若组件在 hydration 期间或用户已输入后再去改 value 属性,dirty flag 已是 true,改属性不再覆盖显示,于是出现「数据对了、框里却是旧值」。框架的受控组件之所以坚持改 .value(IDL)而非属性,正是为了绕开这条 dirty flag 规则。

reset 事件可取消form.reset() 与点击 type=reset 都会先派发一个可取消的 reset 事件;若监听器调用 preventDefault(),后续的还原步骤全部跳过,控件值保持不动。本页 demo 一没有拦截,所以你看到的是原生还原的真实结果;若想做「确认后再重置」,在 reset 监听里按需 preventDefault() 即可。具名访问与控件归属见 <form> 元素