Understanding Vue.js Dependency Collection and the Observer Pattern
This article explains how Vue.js implements its reactivity system using the observer pattern, detailing the roles of Dep and Watcher classes, the observe function, Object.defineProperty handling for objects and arrays, and providing annotated code examples to illustrate the complete dependency‑collection workflow.
Vue.js uses the observer pattern to implement its reactivity system, where a Dep class acts as the observable target and Watcher instances act as observers that react to data changes.
When a component is created, initState calls initData , which in turn invokes observe on the component's data object. observe creates an Observer instance for each object or array, attaches a new Dep to it, and recursively observes nested structures.
The Observer constructor stores the original value, creates a Dep , and for arrays replaces prototype methods (push, pop, shift, unshift, splice, sort, reverse) with wrapped versions that call dep.notify() after mutation. For plain objects it walks each property and calls defineReactive .
defineReactive defines a getter and setter for each property using Object.defineProperty . The getter registers the current watcher (via Dep.target ) by calling dep.depend() , while the setter updates the value, observes new objects, and triggers dep.notify() to update all dependent watchers.
The Dep class maintains a list of subscriber watchers, provides depend() to add the current target, and notify() to invoke update() on each subscriber. Helper functions pushTarget and popTarget manage a stack of active watchers during nested component rendering.
Watcher objects collect dependencies during their get() execution, store them in deps and newDeps sets, and clean up stale dependencies after each update via cleanupDeps() .
Key code snippets:
class Dep {
static target = null
constructor() {
this.id = uid++
this.subs = []
}
addSub(sub) { this.subs.push(sub) }
depend() { if (Dep.target) Dep.target.addDep(this) }
notify() { const subs = this.subs.slice(); for (let i = 0; i < subs.length; i++) subs[i].update() }
} class Observer {
constructor(value) {
this.value = value
this.dep = new Dep()
def(value, '__ob__', this)
if (Array.isArray(value)) {
const augment = hasProto ? protoAugment : copyAugment
augment(value, arrayMethods, arrayKeys)
this.observeArray(value)
} else {
this.walk(value)
}
}
walk(obj) { Object.keys(obj).forEach(key => defineReactive(obj, key, obj[key])) }
observeArray(items) { items.forEach(item => observe(item)) }
} export function defineReactive(obj, key, val, customSetter?, shallow?) {
const dep = new Dep()
const property = Object.getOwnPropertyDescriptor(obj, key)
if (property && property.configurable === false) return
const getter = property && property.get
const setter = property && property.set
let childOb = !shallow && observe(val)
Object.defineProperty(obj, key, {
enumerable: true,
configurable: true,
get() {
const value = getter ? getter.call(obj) : val
if (Dep.target) {
dep.depend()
if (childOb) {
childOb.dep.depend()
if (Array.isArray(value)) dependArray(value)
}
}
return value
},
set(newVal) {
const value = getter ? getter.call(obj) : val
if (newVal === value || (newVal !== newVal && value !== value)) return
if (setter) setter.call(obj, newVal) else val = newVal
childOb = !shallow && observe(newVal)
dep.notify()
}
})
} export default class Watcher {
constructor(vm, expOrFn, cb, options) {
this.vm = vm
vm._watchers.push(this)
this.deps = []
this.newDeps = []
this.depIds = new Set()
this.newDepIds = new Set()
this.getter = expOrFn
this.value = this.lazy ? undefined : this.get()
}
get() {
pushTarget(this)
let value
try { value = this.getter.call(this.vm, this.vm) } finally {
if (this.deep) traverse(value)
popTarget()
this.cleanupDeps()
}
return value
}
addDep(dep) {
const id = dep.id
if (!this.newDepIds.has(id)) {
this.newDepIds.add(id)
this.newDeps.push(dep)
if (!this.depIds.has(id)) dep.addSub(this)
}
}
cleanupDeps() { /* removes stale deps */ }
update() { /* called by Dep.notify */ }
}Together, these mechanisms enable Vue to automatically track which components depend on which pieces of state and efficiently re‑render only the parts of the UI that are affected by data changes.
Xueersi Online School Tech Team
The Xueersi Online School Tech Team, dedicated to innovating and promoting internet education technology.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.