【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 算法

相关推荐
坐吃山猪3 小时前
Electron04-系统通知小闹钟
开发语言·javascript·ecmascript
小飞侠在吗4 小时前
vue toRefs 与 toRef
前端·javascript·vue.js
csuzhucong4 小时前
斜转魔方、斜转扭曲魔方
前端·c++·算法
老华带你飞4 小时前
房屋租赁管理|基于springboot + vue房屋租赁管理系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·毕设
燃烧的土豆4 小时前
100¥ 实现的React项目 Keep-Alive 缓存控件
前端·react.js·ai编程
半生过往4 小时前
前端运行PHP 快速上手 使用 PHPStudy Pro 详细搭建与使用指南
开发语言·前端·php
zlpzlpzyd4 小时前
ecmascript中Promise和async/await的区别
开发语言·前端·ecmascript
streaker3034 小时前
从零实现一个“类微信”表情输入组件
前端·vue.js·element
小明记账簿_微信小程序4 小时前
js、node.js获取指定文件下的内容
前端
小明记账簿_微信小程序4 小时前
h5中弹框出现后禁止页面滚动
前端