重置表单: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> 元素。