在入门篇中,我们知道 Vue2 的核心思想是 数据驱动视图 。那么 Vue2 是如何实现的呢?本篇将带你深入理解 Vue2 的 响应式原理。
目录
- 什么是响应式
- [Vue2 的实现方式](#Vue2 的实现方式)
- [Observer 与数据劫持](#Observer 与数据劫持)
- [Dep 与 Watcher 的关系](#Dep 与 Watcher 的关系)
- 完整流程
- 小结
什么是响应式
所谓响应式:当数据发生变化时,视图会自动更新。
在 Vue2 中,开发者只需要修改 data
中的数据,DOM 就会跟着变化,无需手动操作。
例子:
html
<div id="app">
<p>{{ message }}</p>
<button @click="message = 'Hello Vue!'">修改</button>
</div>
<script>
new Vue({
el: '#app',
data: {
message: '初始数据'
}
})
</script>
点击按钮时,message
改变,页面会自动更新。
Vue2 的实现方式
Vue2 使用 数据劫持 + 发布订阅模式 来实现响应式。
- 数据劫持 :通过
Object.defineProperty
拦截对象属性的读写。 - 发布订阅:当数据变化时,通知依赖该数据的地方更新。
Vue3 则改用 Proxy
,这里我们只讨论 Vue2。
Observer 与数据劫持
Vue 内部会遍历 data
对象,用 Object.defineProperty
给每个属性加上 getter/setter。
js
function defineReactive(obj, key, val) {
Object.defineProperty(obj, key, {
get() {
console.log('获取数据:', key)
return val
},
set(newVal) {
console.log('设置数据:', key, newVal)
val = newVal
// 通知更新
}
})
}
const data = {}
defineReactive(data, 'msg', 'Hello')
console.log(data.msg) // 触发 get
data.msg = 'Hi' // 触发 set
这样就能在属性被访问或修改时执行额外逻辑。
Dep 与 Watcher 的关系
Vue 内部有两个重要角色:
- Dep(依赖收集器):管理一组 Watcher
- Watcher(订阅者):代表一个依赖(比如组件的某个渲染函数)
简单代码模拟:
js
// 1. 定义 Dep 类(依赖收集器)
class Dep {
constructor() {
this.watchers = []; // 存储依赖当前数据的所有 Watcher
}
// 收集依赖:记录 Watcher
addWatcher(watcher) {
this.watchers.push(watcher);
}
// 通知更新:数据变化时,告诉所有 Watcher 执行更新
notify() {
this.watchers.forEach(watcher => watcher.update());
}
}
// 2. 定义 Watcher 类(订阅者)
class Watcher {
constructor(updateFn) {
this.updateFn = updateFn; // 数据变化时要执行的函数(比如重新渲染)
// 初始化时主动触发一次依赖收集
Dep.target = this; // 临时标记当前 Watcher(Vue 内部用这个方式关联 Dep 和 Watcher)
}
// 数据变化时执行的方法
update() {
this.updateFn(); // 调用传入的更新函数(比如重新渲染视图)
}
}
// 3. 模拟 Vue 中的数据(比如 data 里的属性)
const data = {
name: "张三" // 这个属性会对应一个 Dep 实例
};
// 为 data.name 创建对应的 Dep 实例(Vue 会为每个数据属性自动创建 Dep)
const nameDep = new Dep();
// 4. 模拟"组件渲染"场景(依赖 data.name)
// 假设组件渲染时需要读取 data.name,此时会触发依赖收集
function renderComponent() {
console.log(`渲染视图:姓名是 ${data.name}`);
}
// 创建一个 Watcher 关联"渲染函数"(表示这个渲染依赖数据)
new Watcher(() => {
renderComponent(); // 当数据变化时,重新执行渲染
});
// 注意:上面 new Watcher 时,会执行一次 renderComponent,此时会读取 data.name
// 读取时会触发 Dep 收集这个 Watcher(Vue 内部通过 getter 实现这一步)
nameDep.addWatcher(Dep.target); // 手动模拟收集(实际由 Vue 自动完成)
Dep.target = null; // 清除标记
// 5. 现在修改数据,观察效果
console.log("--- 修改 name 为 '李四' ---");
data.name = "李四";
// 数据变化时,Dep 通知所有依赖的 Watcher 更新
nameDep.notify();
// 输出:"渲染视图:姓名是 李四"(视图自动更新)
关系图:
data 属性 ------> Dep ------> Watcher ------> 更新视图
当属性被读取时,Dep 记录下依赖它的 Watcher;
当属性被修改时,Dep 通知所有 Watcher 执行更新。
完整流程
- Vue 初始化时,对
data
中的属性调用Object.defineProperty
。 - 渲染模板时,读取数据,触发 getter,把当前组件的 Watcher 添加到 Dep。
- 修改数据时,触发 setter,Dep 通知所有 Watcher。
- Watcher 执行更新函数,触发视图重新渲染。
这就是 数据变化 → 视图更新 的完整闭环。
小结
- Vue2 响应式基于 Object.defineProperty 实现数据劫持。
- 核心机制:Observer(数据劫持) + Dep(依赖收集器) + Watcher(订阅者)。
- 响应式原理流程:数据劫持 → 依赖收集 → 派发更新。
📗 下一篇进阶文章,我们将学习 虚拟 DOM 与 Diff 算法。