1. 诡异的“消失”现象
让我们还原一个真实的调试场景:
- 你写了一个
<el-input v-model="msg" />。 - 你在页面输入框里输入了攻击脚本:
<script>alert(1)</script>。 - 你右键点击输入框,选择“检查 (Inspect)”。
你预期的画面:
<input class="el-input__inner" value="<script>alert(1)</script>">
你实际看到的画面:
<input class="el-input__inner">
这时候你可能会慌: “完了,我的数据去哪了?Vue 没绑上吗?还是浏览器出 Bug 了?”
真相是:你的数据很安全,只是浏览器“懒”得把它写在脸上。
2. 核心机制:Attribute(特性)vs Property(属性)
这是前端面试中最经典,也最容易被忽略的考点。
2.1 Attribute:写在 HTML 上的“身份证”
我们在 HTML 标签里写的 value="hello",是 Attribute。
- 作用:它代表元素的初始默认值 (Default Value)。
- 特性:除非你显式调用
setAttribute,否则它通常不会随着用户的交互而改变。
2.2 Property:活在 DOM 对象里的“当前状态”
当浏览器加载页面后,会为 <input> 标签创建一个 JS 对象(DOM Node)。这个对象里有一个 value 属性,是 Property。
- 作用:它代表元素的当前实时值 (Current Value)。
- 特性:用户每敲一个字,内存里的这个
value就在变。
2.3 为什么 Elements 面板里看不到?
Chrome 的 Elements 面板虽然展示的是“实时 DOM 树”,但对于 <input> 这种高频交互元素,浏览器有一个“单向同步”策略:
- 从 HTML 到 DOM:页面初始化时,HTML 里的
value会同步给 DOM。 - 从 DOM 到 HTML:NO! 用户输入改变了 DOM 里的
value,浏览器不会反向把它写回 HTML 标签的 attribute 上。
理由很简单:如果每输入一个字母都要去更新 HTML 字符串渲染,性能开销太大了,而且没有必要(因为页面显示已经由渲染引擎处理了)。
3. 那 XSS 到底是怎么防住的?
既然 HTML 标签上没显示,那浏览器到底是怎么渲染这段“攻击代码”的?
答案在于 API 的选择。
Vue 的 v-model 本质是监听 input 事件并更新变量。当它把变量里的 <script> 渲染回屏幕时,它操作的是 DOM Property:
// Vue 底层逻辑(简化版)
inputElement.value = "<script>alert(1)</script>";
关键点来了: 当给 DOM 对象的 value 属性赋值时,浏览器会将其视为纯文本数据。
- 浏览器不会启动 HTML 解析器。
- 浏览器不会尝试寻找
<和>配对。 - 浏览器只是单纯地把这串字符“画”在输入框里。
所以,无论你输入多么复杂的 XSS payload,在 input 框里,它永远只是一串没有任何杀伤力的字符。
4. 如何逼出“真面目”?(查看转义)
虽然 input 标签上不显示,但数据确实在内存里。如果你想亲眼看到浏览器是如何处理这些特殊字符的序列化(转义),你需要用一点小手段:
打开控制台(Console),输入:
// $0 是你当前选中的 input 元素
console.log($0.value); // 输出 "<script>..." <-- 内存里的真值(未转义)
console.log($0.outerHTML); // 输出 "...value="<script>..." <-- 强制序列化后的 HTML
只有当你强制浏览器把当前状态“打印”成 HTML 字符串(outerHTML)时,浏览器为了保证生成的 HTML 语法正确,才会把 < 变成 < 给你看。
5. 总结
- Input 的“懒更新” :Elements 面板里看不到
value变化是正常的,这是浏览器的性能优化策略。 - Vue 的数据流:Vue 绑定的是 DOM Property(内存对象),而不是 HTML Attribute。
- 安全的本质:XSS 之所以失效,是因为 DOM Property (
element.value) 天然把内容当作纯文本处理,直接绕过了 HTML 解析阶段。
下次再看到空的 input 标签别慌,你的数据正安全地躺在内存里呢。