SoFunction
Updated on 2025-04-06

In-depth understanding of the principles of Vue Computed computing properties

Computed Computed properties are a common feature in Vue, but do you understand how it works?

Take a simple example from the official website:

<div >
 <p>Original message: "{{ message }}"</p>
 <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
 el: '#example',
 data: {
  message: 'Hello'
 },
 computed: {
  // a computed getter
  reversedMessage: function () {
   // `this` points to the vm instance
   return ('').reverse().join('')
  }
 }
})

Situation

The Computed property in Vue is used very frequently, but it is not very clear about its implementation principle. For example: How to establish dependencies between computed attributes? How to notify the calculation attribute to recalculate when the attribute changes?

Regarding how to establish dependencies, the first thing I think of is syntax parsing, but this is too wasteful of performance, so I excluded it. The second thing I think of is to use the single-threading principle of JavaScript and Vue's Getter design. Through a simple publish subscription, you can collect relevant dependencies in the process of calculating attribute evaluation.

Therefore, the next task is to analyze the implementation principle of Computed step by step from the Vue source code.

Task

Analyze the principle of dependency collection and analysis the principle of dynamic calculation.

Action

The data attribute initialization getter setter:

// src/observer/

// Here we start converting the getter setter of data, the original value has been saved in the __ob__ attribute(obj, key, {
 enumerable: true,
 configurable: true,
 get: function reactiveGetter () {
  const value = getter ? (obj) : val
  // Determine whether it is in a dependency collection state  if () {
   // Create dependencies   ()
   ...
  }
  return value
 },
 set: function reactiveSetter (newVal) {
  ...
  // Dependency changes, notification to the calculation attribute to recalculate  ()
 }
})

computed Compute attribute initialization

// src/core/instance/

// Initialize the calculation propertiesfunction initComputed (vm: Component, computed: Object) {
 ...
 // traversal computed properties for (const key in computed) {
  ...
  // Create a Watcher instance  // create internal watcher for the computed property.
  watchers[key] = new Watcher(vm, getter || noop, noop, computedWatcherOptions)

  // Create a property and use the provided function as a getter for the property,  // Finally, computed and data will be mixed together under vm, so a warning will be thrown when there is a duplicate attribute between computed and data.  defineComputed(vm, key, userDef)
  ...
 }
}

export function defineComputed (target: any, key: string, userDef: Object | Function) {
 ...
 // Create a get set method  = createComputedGetter(key)
  = noop
 ...
 // Create attributes and initialize getter setter (target, key, sharedPropertyDefinition)
}

function createComputedGetter (key) {
 return function computedGetter () {
  const watcher = this._computedWatchers &amp;&amp; this._computedWatchers[key]
  if (watcher) {
   if () {
    // watcher exposes evaluation method for value operation    ()
   }
   // Same as step 1, determine whether it is in a dependency collection state   if () {
    ()
   }
   return 
  }
 }
}

Whether it is a property or a computed property, a corresponding watcher instance will be generated.

// src/core/observer/

// When obtaining the computed attributes throughget () {
 // this refers to the watcher instance // Stories the current watcher instance to , which means that the dependency collection task is enabled pushTarget(this)
 let value
 const vm = 
 try {
  // When executing the function function, the getter of the attribute (step 1) and the calculated attribute (step 2) will be triggered  // During this execution process, you can collect the dependencies  value = (vm, vm)
 } catch (e) {
  if () {
   handleError(e, vm, `getter for watcher "${}"`)
  } else {
   throw e
  }
 } finally {
  if () {
   traverse(value)
  }
  // End the dependency collection task  popTarget()
  ()
 }
 return value
}

The above mentioned , , , , so what exactly is Dep?

Dep's code is short and concise, but it undertakes a very important dependency collection process.

// src/core/observer/

export default class Dep {
 static target: ?Watcher;
 id: number;
 subs: Array&lt;Watcher&gt;;

 constructor () {
   = uid++
   = []
 }

 addSub (sub: Watcher) {
  (sub)
 }

 removeSub (sub: Watcher) {
  remove(, sub)
 }

 depend () {
  if () {
   (this)
  }
 }

 notify () {
  const subs = ()
  for (let i = 0, l = ; i &lt; l; i++) {
   // Update the value of watcher, similar to (),   // But update is used when dependent changes, including processing of watch   subs[i].update()
  }
 }
}

// When the value of the computed attribute is first calculated, Dep will collect the dependencies during the calculation period = null
const targetStack = []

export function pushTarget (_target: Watcher) {
 // During a dependency collection period, if other dependency collection tasks start (for example: the current computed computed attribute is nested with other computed computed attributes), // Then the current target will be temporarily stored in the targetStack, and the dependency collection of other targets will be performed first. if () ()
  = _target
}

export function popTarget () {
 // After the nested dependency collection task is completed, restore the target to the previous layer of Watcher and continue to do dependency collection  = ()
}

Result

Summarize the process of dependency collection and dynamic calculation:

1. Data property initialization getter setter

2. computed calculates attribute initialization, and the provided function will be used as the getter for the attribute

3. When the reversedMessage calculation property is first obtained, Dep starts to depend on collection

4. When executing the message getter method, if Dep is in the dependency collection state, the message is determined to be a dependency of reversedMessage and establish a dependency relationship.

5. When message changes, trigger recalculation of reverseMessage based on dependencies.
At this point, the entire Computed workflow has been clarified.

Vue is a framework with very beautiful design. Using Getter Setter design makes dependencies implement very natural. Using a design that separates computing and rendering (priority use of MutationObserver, downgrading use of setTimeout) is also very consistent with the design principle of separation between the browser's computing engine and the layout engine.

If you want to become an architect, you can't just stay at the API usage level of the framework.

The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.