【Vue2 ✨】Vue2 入门之旅 · 进阶篇(一):响应式原理

在入门篇中,我们知道 Vue2 的核心思想是 数据驱动视图 。那么 Vue2 是如何实现的呢?本篇将带你深入理解 Vue2 的 响应式原理


目录

  1. 什么是响应式
  2. [Vue2 的实现方式](#Vue2 的实现方式)
  3. [Observer 与数据劫持](#Observer 与数据劫持)
  4. [Dep 与 Watcher 的关系](#Dep 与 Watcher 的关系)
  5. 完整流程
  6. 小结

什么是响应式

所谓响应式:当数据发生变化时,视图会自动更新。

在 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 执行更新。


完整流程

  1. Vue 初始化时,对 data 中的属性调用 Object.defineProperty
  2. 渲染模板时,读取数据,触发 getter,把当前组件的 Watcher 添加到 Dep。
  3. 修改数据时,触发 setter,Dep 通知所有 Watcher。
  4. Watcher 执行更新函数,触发视图重新渲染。

这就是 数据变化 → 视图更新 的完整闭环。


小结

  1. Vue2 响应式基于 Object.defineProperty 实现数据劫持。
  2. 核心机制:Observer(数据劫持) + Dep(依赖收集器) + Watcher(订阅者)
  3. 响应式原理流程:数据劫持 → 依赖收集 → 派发更新。

📗 下一篇进阶文章,我们将学习 虚拟 DOM 与 Diff 算法

相关推荐
q***47181 天前
解决 Tomcat 跨域问题 - Tomcat 配置静态文件和 Java Web 服务(Spring MVC Springboot)同时允许跨域
java·前端·spring
小光学长1 天前
基于Web的课前问题导入系统pn8lj4ii(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·前端·数据库
Mintopia1 天前
🌐 跨模态迁移学习:WebAIGC多场景适配的未来技术核心
前端·javascript·aigc
JarvanMo1 天前
使用 MediaPipe 在 Flutter web 中识别姿势
前端
saadiya~1 天前
基于 Vue3 封装大华 RTSP 回放视频组件(PlayerControl.js 实现)
前端·vue3·大华视频相机前端播放
LSL666_1 天前
spring多配置文件
java·服务器·前端·spring
万少1 天前
HarmonyOS preview 预览文件 Kit 的入门讲解
前端
IT_陈寒1 天前
JavaScript 性能优化实战:我从 V8 源码中学到的 7 个关键技巧
前端·人工智能·后端
jenchoi4131 天前
软件供应链npm/pypi投毒预警情报【2025-11-09】
前端·安全·web安全·网络安全·npm·node.js
艾小码1 天前
别再只会用默认插槽了!Vue插槽这些高级用法让你的组件更强大
前端·javascript·vue.js