大家好,我是小杨,干了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 对数组的 push
、pop
等方法做了 hack,但仍然有局限性:
javascript
data.list.push('new item') // 能触发更新(因为 Vue 重写了数组方法)
data.obj.newKey = 'value' // 不会触发更新!(Vue2 监听不到新增属性)
所以 Vue2 提供了 Vue.set
和 this.$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 函数
流程:
- 组件渲染时,触发
getter
,Dep 记录当前 Watcher - 数据变化时,触发
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,而是:
- 生成新的虚拟 DOM
- 和旧的虚拟 DOM 对比(Diff)
- 只更新真正变化的部分
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
最后感谢各位的耐心观看,既然都到这了,点个 👍赞再走吧!