前端面试之MVVM浅析|原理篇
30 秒速记
- 核心判断:响应式系统由依赖收集、变更触发和调度刷新组成,关键不在“监听数据”,而在精确关联状态与副作用
- 原理主线:围绕 「一、说一下使用 jquery 和使用框架」、「二、说一下对 MVVM 的理解」、「三、vue 中如何实现响应式」 建立输入、状态变化与输出之间的因果关系
- 文章范围:浅出地分析了前端开发中MVVM模式的原理,比较了jQuery与现代前端框架的区别,详细讲解了MVC与MVVM的结构、核心概念及其在前端开发中的应用,帮助读者理解数据驱动视图和响应式开发的优势。
- 边界与代价:
Vue 2的defineProperty与Vue 3的Proxy在新增属性、数组和集合类型上边界不同 - 工程落地:排查视图不更新或重复更新时,应沿读取是否被追踪、写入是否触发、任务是否被去重三段定位
MVVM 通过 ViewModel 连接数据和视图,让开发者更新状态而不是手动操作 DOM。 模板会先被编译成 render 函数,执行后生成 vnode;首次渲染读取响应式属性时完成依赖收集。属性被修改后,响应式的 set 触发重新渲染,再由 patch 对比新旧 vnode 并更新页面。监听 get 的意义是只关联真正被视图使用的数据,避免无关状态变化也触发重复渲染。
这篇文章不要按 API 清单来背。先用上面的 Mind Map 建立全局结构,再通过交互 DEMO 观察正常路径和边界路径如何改变状态;阅读正文时重点核对每一步的输入、负责执行的参与者、产生的中间状态以及最终可观察结果。遇到版本敏感结论,要把“历史实现”“当前行为”和“工程兼容策略”分开说明;遇到性能或架构取舍,则用实际指标、失败现象和验证手段支撑判断。
版本校准: 原理文章中的代码代表特定实现与写作时间。应用到当前项目时,应先确认浏览器、框架或工具的主版本,再区分稳定的规范语义、可变化的内部实现和项目自身约束。

# 一、说一下使用 jquery 和使用框架的区别
# 1.1 jQuery 实现 todo-list

# 1.2 vue 实现 todo-list

# 1.3 jQuery 和框架的区别
- 数据和视图的分离,解耦(开放封闭原则)
- 以数据驱动视图,只关心数据变化,DOM 操作被封装
# 二、说一下对 MVVM 的理解
# 2.4 MVVM 框架的三大要素
- 响应式:
vue如何监听到data的每个属性变化? - 模板引擎:
vue的模板如何被解析,指令如何处理? - 渲染:
vue的模板如何被渲染成html?以及渲染过程
# 三、vue 中如何实现响应式
# 3.2 Object.defineProperty

# 四、vue 中如何解析模板
# 4.1 模板是什么
- 本质:字符串
- 有逻辑,如
v-ifv-for等 - 与
html格式很像,但有很大区别 - 最终还要转换为
html来显示
模板最终必须转换成 JS 代码,因为
- 有逻辑(
v-ifv-for),必须用JS才能实现 - 转换为
html渲染页面,必须用JS才能实现 - 因此,模板最重要转换成一个
JS函数(render函数)

# 4.2 render 函数
- 模板中所有信息都包含在了
render函数中 this即vmprice即this.price即vm.price,即data中的price_c即this._c即vm._c



# 4.3 render 函数与 vdom
vm._c其实就相当于snabbdom中的h函数render函数执行之后,返回的是vnode


updateComponent中实现了vdom的patch- 页面首次渲染执行
updateComponent data中每次修改属性,执行updateComponent
# 5.1 第一步:解析模板成 render 函数




- 模板中的所有信息都被
render函数包含 - 模板中用到的
data中的属性,都变成了JS变量 - 模板中的
v-modelv-forv-on都变成了JS逻辑 render函数返回vnode
# 5.3 第三步:首次渲染,显示页面,且绑定依赖
- 初次渲染,执行
updateComponent,执行vm._render() - 执行
render函数,会访问到vm.list vm.title - 会被响应式的
get方法监听到 - 执行
updateComponent,会走到vdom的patch方法 patch将vnode渲染成DOM,初次渲染完成

为何要监听 get ,直接监听 set 不行吗?
data中有很多属性,有些被用到,有些可能不被用到- 被用到的会走到
get,不被用到的不会走到get - 未走到
get中的属性,set的时候我们也无需关心 - 避免不必要的重复渲染

# 5.4 第四步:data 属性变化

- 修改属性,被响应式的
set监听到 set中执行updateComponent- updateComponent 重新执行
vm._render() - 生成的
vnode和prevVnode,通过patch进行对比 - 渲染到
html中

面试官追问
追问 1商品详情页修改一个已响应式化的价格字段后,控制台值已变化,但有人说浏览器会直接改对应 DOM;这条解释漏掉了哪些环节?
属性写入先被响应式 set 捕获,再触发 updateComponent,由 vm._render() 生成新的 vnode。随后新旧 vnode 经 patch 对比才落实到 HTML;数据变化并不意味着绕过虚拟节点直接操作目标 DOM。
追问 2表格页一次更新 300 行状态,工程师准备在每个 set 中手写 document.querySelector 更新单元格,你会如何基于现有渲染链路反驳?
响应式写入应沿 set、updateComponent、vm._render() 和 patch 的既有链路更新页面,无需为每个字段另建命令式 DOM 通道。手写更新会形成第二套状态来源,容易与下一次虚拟节点渲染互相覆盖,规模增大后维护代价更高。
追问 3仪表盘原来只改一个字段,现在一次提交会改 50 个字段;如果每次 set 都立即完整执行 updateComponent,你最担心什么?
最直接的风险是同一轮连续写入引发重复渲染和多次新旧 vnode 对比,产生无必要的工作。源码小结只给出了触发链路,没有说明批处理策略,因此实现时应确认是否存在调度与合并机制,不能仅凭该段断言具体执行次数。
追问 4线上反馈筛选条件已经变化但列表还是旧内容,你会沿 set 到 patch 的链路怎样定位断点?
先验证目标属性是否确实经过响应式 set,再观察 updateComponent 与 vm._render() 是否执行,并比较生成的 vnode 是否包含新列表。若虚拟节点正确而页面未变,再查 patch 对比与落地;若虚拟节点已旧,问题更可能在状态读取或渲染逻辑。
追问 5组件负责人想在数据变化后整页重建 HTML,另一人坚持走 vnode 对比;在一个交互频繁的列表页里你怎么取舍?
既有机制会生成新 vnode 并与 prevVnode 通过 patch 对比,因此应优先沿这条链路做增量更新。整页重建虽然实现直观,却放弃了差异比较,还可能破坏页面中的局部交互状态;只有脱离该渲染体系时才需另行评估。








