前言
Vue.js 渐进式JavaScript框架,以其简洁和高效著称。当一个Vue实例被创建时,Vue会遍历其data对象中的所有属性,并使用Object.defineProperty方法将它们转换为getter/setter。这样,当我们修改data对象中的属性时,Vue就能够捕获到这个变化,并触发相应的更新操作。这一神奇的响应式特性是如何实现的呢?本文将深入探索Vue响应式系统的实现原理,揭开其背后的神秘面纱。
发布-订阅模式
在 Vue 中,当我们修改状态时,视图会随之更新,这背后依赖就是Vue的响应式系统。Vue的响应式原理是通过数据劫持和发布-订阅模式实现的。
- 发布者
发布者主要是指响应式数据。通过使用Object.defineProperty方法将数据转换为getter/setter,并在setter函数中通知依赖于该数据的Watcher对象进行更新操作,Vue能够实现高效、可靠、灵活的响应式数据绑定。
- 订阅者
Watcher对象是一个订阅者,它会监听数据变化并执行相应的回调函数。Vue中有三种类型的Watcher:
-
计算属性Watcher:计算属性Watcher会监听其依赖的数据变化,并在需要时重新计算计算属性的值。当计算属性被访问时,如果其依赖的数据发生了变化,则会触发计算属性Watcher进行更新操作。 -
监听器Watcher:监听器Watcher会监听指定的数据变化,并在数据发生变化时执行相应的回调函数。 -
渲染函数Watcher:渲染函数Watcher会监听组件中使用到的所有响应式数据,并在这些数据发生变化时重新渲染组件。每个组件都有一个渲染函数Watcher。

Observe 观察函数
在Vue源码中,Observe函数定义在core/observer/index.js文件中。该函数接收一个参数value,用于将value对象转换为响应式对象。Observe遍历递归对象每一个属性,并将其转化为getter和setter函数,以便于在属性被访问时或修改时进行依赖收集和派发更新。
// `Observe`数据劫持
class Observer {
constructor(value){
this.value = value
this.walk(value)
}
// 遍历对象的所有属性,调用 defineReactive
walk(obj) {
const keys = Object.keys(obj)
for (let i = 0; i < keys.length; i++) {
defineReactive(obj, keys[i])
}
}
}
// 将对象的属性转化为 getter 和 setter
function defineReactive(obj, key) {
const dep = new Dep() // 创建依赖收集类
let val = obj[key]
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
dep.depend() // 依赖收集
return val
},
set(newVal) {
if (newVal === val) return
val = newVal
dep.notify() // 派发更新
}
})
}
Dep 收集依赖和派发更新
Dep 是一个核心类,用于依赖收集和派发更新。它的主要作用是维护一个订阅者列表(即依赖),通过依赖收集和派发更新,实现了数据变化自动触发视图更新的功能。
// 初始化 `Dep` 实例,设置唯一的 `id`
let uid = 0
// `Dep`发布者
class Dep {
constructor() {
this.id = uid++
this.subs = []
}
// 添加订阅者
addSub(sub) {
this.subs.push(sub)
}
// 移除订阅者
removeSub(sub) {
let index = this.subs.indexOf(sub)
if (index > -1) {
this.subs.splice(index, 1)
}
}
// 将当前的 `Dep` 实例添加到 `Dep.target` 的依赖列表中
depend() {
if (Dep.target) {
Dep.target.addDep(this)
}
}
// 通知所有订阅者,调用它们的 `update` 方法
notify() {
this.subs.forEach(watcher => {
watcher.update()
})
}
}
// 用于临时存储当前正在计算的目标订阅者(通常是一个 `Watcher` 实例)
Dep.target = null
Watcher 监听依赖和更新视图
Watcher对象是一个订阅者,它会监听数据变化并执行相应的回调函数。当数据发生变化时,在setter函数中通知依赖于该数据的Watcher对象进行更新操作,Watcher对象会接收到通知并执行回调函数来更新视图。
// `Watcher`订阅者
class Watcher {
constructor(vm, expOrFn, cb) {
this.vm = vm
this.cb = cb
this.depIds = new Set()
this.getter = parsePath(expOrFn)
this.value = this.get()
}
// 获取当前值并进行依赖收集
get() {
Dep.target = this
const value = this.getter.call(this.vm, this.vm)
Dep.target = null
return value
}
// 添加依赖
addDep(dep) {
if (!this.depIds.has(dep.id)) {
this.depIds.add(dep.id)
dep.addSub(this)
}
}
// 更新视图
update() {
const value = this.get()
const oldValue = this.value
this.value = value
this.cb.call(this.vm, value, oldValue)
}
}
简单小例子
// 响应式数据
const data = { price: 5, quantity: 2 };
// 依赖收集
new Observer(data);
// 监听依赖
new Watcher(data, 'price', (newVal, oldVal) => {
console.log(`Price changed from ${oldVal} to ${newVal}`);
});
data.price = 10; // 触发更新,打印 "Price changed from 5 to 10"
总结
总结一下,Vue 响应式原理主要包括以下几个步骤:
- 数据劫持:遍历
data对象中的所有属性,并使用Object.defineProperty方法将它们转换为getter/setter; - 依赖收集:当我们访问
data对象中的属性时,Vue 会触发getter函数,进行依赖收集; - 派发更新:当我们修改
data对象中的属性时,Vue 会触发setter函数,通知依赖于该属性的所有Watcher对象进行更新操作; - 视图更新:
Watcher对象接收到通知后执行回调函数来更新视图。
通过这种方式,Vue能够实现高效、灵活、可靠的响应式数据绑定。
end:才疏学浅,如有讲述不清之处,还请指正,共同进步。






