目录
[一、什么是 MVVM?](#一、什么是 MVVM?)
[1. Model(模型)](#1. Model(模型))
[2. View(视图)](#2. View(视图))
[3. ViewModel(视图模型)](#3. ViewModel(视图模型))
[四、Vue 实例(ViewModel)](#四、Vue 实例(ViewModel))
[Vue 实例是什么?](#Vue 实例是什么?)
[Vue2 vs Vue3 实例创建](#Vue2 vs Vue3 实例创建)
[五、Vue 如何实现双向绑定?](#五、Vue 如何实现双向绑定?)
[Q:什么是 MVVM?](#Q:什么是 MVVM?)
[Q:MVVM 和 MVC 的区别?](#Q:MVVM 和 MVC 的区别?)
[Q:Vue 实例是什么时候创建的?](#Q:Vue 实例是什么时候创建的?)
[Q:Vue2 和 Vue3 创建组件实例有什么区别?](#Q:Vue2 和 Vue3 创建组件实例有什么区别?)
[七、为什么 Vue3 要取消 this?](#七、为什么 Vue3 要取消 this?)
[原因1:this 容易让人困惑](#原因1:this 容易让人困惑)
[原因2:TypeScript 类型推导更简单](#原因2:TypeScript 类型推导更简单)
一、什么是 MVVM?
MVVM 是一种架构模式,实现了视图和数据的双向绑定:数据变视图自动变,视图变数据自动变。
┌─────────────────────────────────────────────────────────────┐
│ MVVM 架构 │
│ │
│ ┌─────────┐ ┌─────────────┐ ┌─────────┐ │
│ │ Model │ ◄─────► │ ViewModel │ ◄─────► │ View │ │
│ │ 数据 │ │ Vue 实例 │ │ 视图 │ │
│ └─────────┘ └─────────────┘ └─────────┘ │
│ ↑ ↑ ↑ │
│ │ │ │ │
│ 原始数据 响应式系统 DOM │
│ API返回 双向绑定 页面 │
│ │
└─────────────────────────────────────────────────────────────┘
二、三部分详解
1. Model(模型)
| 要点 | 说明 |
|---|---|
| 是什么 | 数据层 |
| 包含 | 后端接口返回的数据、本地数据、状态 |
| 职责 | 只关心数据,不关心页面 |
| 例子 | data() { return { count: 0 } } |
2. View(视图)
| 要点 | 说明 |
|---|---|
| 是什么 | 视图层 |
| 包含 | 页面、DOM、用户看到的界面 |
| 职责 | 只负责展示,不直接操作数据 |
| 例子 | <div>{``{ count }}</div> |
3. ViewModel(视图模型)
| 要点 | 说明 |
|---|---|
| 是什么 | Vue 实例 |
| 职责 | 连接 Model 和 View 的中间层,实现双向绑定 |
| 核心能力 | 数据变化 → 更新视图;视图操作 → 更新数据 |
| 例子 | Vue 组件实例 |
三、核心亮点:双向绑定
传统做法(手动操作 DOM):
javascript
// 没有 MVVM 时
const input = document.getElementById('input')
const span = document.getElementById('span')
// 数据 → 视图
span.innerText = data
// 视图 → 数据
input.addEventListener('input', (e) => {
data = e.target.value
span.innerText = data // 还要手动更新视图
})
MVVM 做法(自动双向绑定):
javascript
<template>
<div>
<input v-model="message" />
<p>{{ message }}</p>
</div>
</template>
<script setup>
const message = ref('')
// 不用写任何 DOM 操作代码!
</script>
四、Vue 实例(ViewModel)
Vue 实例是什么?
每个 Vue 组件,本质上就是一个 Vue 实例。它是一个对象,掌管着当前组件里的一切。
| 掌管内容 | 说明 |
|---|---|
data |
响应式数据 |
methods |
方法 |
computed |
计算属性 |
watch |
侦听器 |
| 生命周期 | 钩子函数 |
| DOM 更新 | 虚拟 DOM、渲染 |
| 事件监听 | 原生事件、自定义事件 |
v-model |
双向绑定 |
Vue2 vs Vue3 实例创建
javascript
// Vue2
const vm = new Vue({
data: { count: 0 },
methods: { increment() { this.count++ } }
})
// vm 就是 ViewModel
// Vue3
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App) // 根实例
app.mount('#app')
// Vue3 组件实例依然存在,但被隐藏,不再暴露 this
组件实例创建时机
┌─────────────────────────────────────────────────────────────┐
│ 组件实例创建规则: │
│ │
│ 1. 根实例:手动创建(new Vue() / createApp()) │
│ │
│ 2. 其他组件实例:不是一开始就创建! │
│ 而是在组件被渲染到视图时,由 Vue 自动创建 │
│ │
│ 3. 一个组件被渲染几次,就创建几个实例 │
│ │
│ 例如: │
│ <UserCard v-for="user in users" :key="user.id" /> │
│ 有 10 个用户,就创建 10 个 UserCard 实例 │
└─────────────────────────────────────────────────────────────┘
五、Vue 如何实现双向绑定?
┌─────────────────────────────────────────────────────────────┐
│ Vue 双向绑定原理 │
│ │
│ 数据变化 → 视图更新(响应式系统) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 数据变化 │ → │ 触发 set│ → │ 通知更新│ → 重新渲染 │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ 视图变化 → 数据更新(事件监听) │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ 用户输入 │ → │ 触发事件│ → │ 更新数据│ → 响应式触发 │
│ └─────────┘ └─────────┘ └─────────┘ │
│ │
│ v-model 就是连接这两套机制的语法糖 │
└─────────────────────────────────────────────────────────────┘
六、面试高频问题
Q:什么是 MVVM?
答:
MVVM 是一种架构模式,由三部分组成:
Model:数据层
View:视图层
ViewModel:连接层(Vue 实例)
核心特性是双向绑定:数据变化自动更新视图,视图变化自动更新数据。
Q:MVVM 和 MVC 的区别?
| 对比 | MVC | MVVM |
|---|---|---|
| 组成部分 | Model、View、Controller | Model、View、ViewModel |
| 数据流向 | 单向 | 双向 |
| 核心 | Controller 控制逻辑 | ViewModel 自动同步 |
| DOM 操作 | 需要手动 | 自动 |
Q:Vue 实例是什么时候创建的?
答:
根实例:手动创建(
new Vue()或createApp())其他组件实例:在组件被渲染时自动创建
一个组件被渲染几次,就创建几个实例
Q:Vue2 和 Vue3 创建组件实例有什么区别?
答:
根实例创建方式不同 :Vue2 用
new Vue(),Vue3 用createApp()实例暴露程度不同 :Vue2 完全暴露实例,通过
this访问;Vue3 隐藏实例,<script setup>中没有this实例创建时机相同:都是组件渲染时动态创建,渲染几次就创建几个实例
API 风格不同:Vue2 推荐 Options API,Vue3 推荐 Composition API
| 对比项 | Vue2 | Vue3 (<script setup>) |
|---|---|---|
| 有没有 this | ✅ 有 | ❌ 没有 |
| 访问数据 | this.count |
count |
| 访问方法 | this.increment() |
increment() |
| 访问 DOM | this.$el |
用 ref |
| 触发事件 | this.$emit() |
defineEmits() |
| TypeScript | 支持较弱 | 完美支持 |
| 学习曲线 | 需要理解 this | 像写普通 JS |
七、为什么 Vue3 要取消 this?
原因1:this 容易让人困惑
javascript
// Vue2 中的坑
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
// 普通函数:this 正确
setTimeout(function() {
this.count++ // ❌ 这里的 this 是 window!不是组件!
}, 1000)
// 必须用箭头函数
setTimeout(() => {
this.count++ // ✅ 箭头函数 this 正确
}, 1000)
}
}
}
javascript
// Vue3:没有 this,永远不会有这个问题
const count = ref(0)
function increment() {
setTimeout(() => {
count.value++ // ✅ 直接用变量,永远正确
}, 1000)
}
原因2:TypeScript 类型推导更简单
javascript
// Vue2:TypeScript 很难推导 this 的类型
export default {
data() {
return {
count: 0
}
},
methods: {
increment() {
this.count // this 是什么类型?很复杂
}
}
}
// Vue3:TypeScript 完美推导
const count = ref(0) // 类型自动是 Ref<number>
function increment() {
count.value // 类型明确
}
原因3:代码更清晰
javascript
// Vue2:数据和方法分散在不同的区域
export default {
data() {
return {
count: 0,
user: null,
loading: false
}
},
methods: {
increment() { this.count++ },
fetchUser() { ... }
}
}
// Vue3:相关逻辑可以放在一起
const count = ref(0)
const increment = () => count.value++
const user = ref(null)
const loading = ref(false)
const fetchUser = async () => { ... }