Frontend Development 16 min read

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.

Xueersi Online School Tech Team
Xueersi Online School Tech Team
Xueersi Online School Tech Team
Understanding Vue.js Dependency Collection and the Observer Pattern

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.

frontendVue.jsObserver PatternReactivityDependency Collection
Xueersi Online School Tech Team
Written by

Xueersi Online School Tech Team

The Xueersi Online School Tech Team, dedicated to innovating and promoting internet education technology.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.