手撕vue底层响应式原理!

大家好,我是小杨,干了6年前端,从 jQuery 一路干到 Vue3,被它的"响应式魔法"惊艳了无数次。今天,我们就来手撕 Vue 的底层原理,看看它到底是怎么做到"数据一变,视图自动更新"的!


一、Vue 的核心绝活:响应式

你有没有想过,为什么我们在 Vue 里改个数据,页面就自动更新了?

javascript 复制代码
data() {
  return {
    message: 'Hello, Vue!'
  }
},
methods: {
  changeMessage() {
    this.message = '我被修改了!' // 页面自动更新!
  }
}

这背后,其实是 Vue 的响应式系统在搞事情!


二、Vue2 的响应式:Object.defineProperty

Vue2 的响应式核心是 Object.defineProperty,也就是数据劫持

1. 基本实现

javascript 复制代码
let data = { message: 'Hello' }

Object.defineProperty(data, 'message', {
  get() {
    console.log('读取 message')
    return this._message
  },
  set(newValue) {
    console.log('修改 message')
    this._message = newValue
    // 这里可以触发视图更新!
  }
})

data.message = 'Vue2' // 控制台输出:修改 message

2. 问题:数组和深层对象监听不了

Vue2 对数组的 pushpop 等方法做了 hack,但仍然有局限性:

javascript 复制代码
data.list.push('new item') // 能触发更新(因为 Vue 重写了数组方法)
data.obj.newKey = 'value' // 不会触发更新!(Vue2 监听不到新增属性)

所以 Vue2 提供了 Vue.setthis.$set 来手动触发更新。


三、Vue3 的响应式:Proxy

Vue3 用 Proxy 重构了响应式系统,解决了 Vue2 的痛点。

1. Proxy 基本用法

javascript 复制代码
let data = { message: 'Hello' }

let proxy = new Proxy(data, {
  get(target, key) {
    console.log(`读取 ${key}`)
    return target[key]
  },
  set(target, key, value) {
    console.log(`修改 ${key}`)
    target[key] = value
    // 触发更新
    return true
  }
})

proxy.message = 'Vue3' // 控制台输出:修改 message

2. 优势:监听动态属性和数组

javascript 复制代码
proxy.newKey = '动态新增属性也能监听!' // 生效!
proxy.list = [1, 2, 3]
proxy.list.push(4) // 生效!(Vue3 不需要 hack 数组方法)

Proxy 比 defineProperty 强在哪?

✅ 能监听新增/删除属性

✅ 能直接监听数组变化

✅ 性能更好(不用递归遍历对象)


四、依赖收集与派发更新

Vue 怎么知道哪些组件需要更新?答案是依赖收集

1. 依赖收集(Dep + Watcher)

  • Dep(依赖管理器) :每个响应式属性都有一个 Dep,用来存放依赖它的 Watcher
  • Watcher(观察者) :代表一个依赖,比如组件的 render 函数

流程:

  1. 组件渲染时,触发 getter,Dep 记录当前 Watcher
  2. 数据变化时,触发 setter,Dep 通知所有 Watcher 更新

2. 代码模拟(简化版)

javascript 复制代码
class Dep {
  constructor() {
    this.subscribers = new Set()
  }
  depend() {
    if (activeWatcher) {
      this.subscribers.add(activeWatcher)
    }
  }
  notify() {
    this.subscribers.forEach(watcher => watcher.update())
  }
}

let activeWatcher = null

class Watcher {
  constructor(updateFn) {
    this.updateFn = updateFn
    this.run()
  }
  run() {
    activeWatcher = this
    this.updateFn()
    activeWatcher = null
  }
  update() {
    this.run()
  }
}

// 使用示例
const dep = new Dep()
const data = { count: 0 }

new Watcher(() => {
  console.log(`count 的值是:${data.count}`)
  dep.depend() // 收集依赖
})

data.count = 1 // 输出:count 的值是:1

五、虚拟 DOM 与 Diff 算法

Vue 并不是数据一变就直接改 DOM,而是:

  1. 生成新的虚拟 DOM
  2. 和旧的虚拟 DOM 对比(Diff)
  3. 只更新真正变化的部分

Diff 算法的核心策略

  • 同层比较(不跨层级对比)
  • key 的重要性(帮助 Vue 识别节点复用)
javascript 复制代码
// 没有 key 的情况(低效)
<ul>
  <li v-for="item in list">{{ item }}</li>
</ul>

// 有 key 的情况(高效复用)
<ul>
  <li v-for="item in list" :key="item.id">{{ item.text }}</li>
</ul>

六、总结

Vue2 用 Object.defineProperty 做响应式,但监听不了动态属性和数组

Vue3 用 Proxy 重构,完美解决 Vue2 的痛点

依赖收集(Dep + Watcher)让 Vue 知道哪些组件需要更新

虚拟 DOM + Diff 算法让更新更高效

⭐ 写在最后

请大家不吝赐教,在下方评论或者私信我,十分感谢🙏🙏🙏.

✅ 认为我某个部分的设计过于繁琐,有更加简单或者更高逼格的封装方式

✅ 认为我部分代码过于老旧,可以提供新的API或最新语法

✅ 对于文章中部分内容不理解

✅ 解答我文章中一些疑问

✅ 认为某些交互,功能需要优化,发现BUG

✅ 想要添加新功能,对于整体的设计,外观有更好的建议

✅ 一起探讨技术加qq交流群:906392632

最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!

相关推荐
岁月宁静2 小时前
深度定制:在 Vue 3.5 应用中集成流式 AI 写作助手的实践
前端·vue.js·人工智能
心易行者3 小时前
10天!前端用coze,后端用Trae IDE+Claude Code从0开始构建到平台上线
前端
saadiya~3 小时前
ECharts 实时数据平滑更新实践(含 WebSocket 模拟)
前端·javascript·echarts
fruge4 小时前
前端三驾马车(HTML/CSS/JS)核心概念深度解析
前端·css·html
百锦再4 小时前
Vue Scoped样式混淆问题详解与解决方案
java·前端·javascript·数据库·vue.js·学习·.net
烛阴4 小时前
Lua 模块的完整入门指南
前端·lua
Sheldon一蓑烟雨任平生4 小时前
Vue3 表单输入绑定
vue.js·vue3·v-model·vue3 表单输入绑定·表单输入绑定·input和change区别·vue3 双向数据绑定
浪里行舟5 小时前
国产OCR双雄对决?PaddleOCR-VL与DeepSeek-OCR全面解析
前端·后端
znhy@1235 小时前
CSS易忘属性
前端·css
瓜瓜怪兽亚5 小时前
前端基础知识---Ajax
前端·javascript·ajax