“This is the 24th day of my participation in the Gwen Challenge in November. Check out the details: The Last Gwen Challenge in 2021.”

preface

In Vue, computed property is one of the features we use a lot, which is similar to Watch, but very different. Computed is the last and most complex instantiation of the Watcher class. Here we read the source code to see how it works.

Watcher

Computed is responsive data, so computed uses the Watcher class when it’s initialized and when it’s used, so let’s just say Watcher.

Watcher works by setting itself to a globally unique location (windonw.target) and then reading data. Because the data is read, the getter for that data is triggered. The watcher that actually reads the data is then read from the globally unique location in the getter, and the wathcer is collected into the Dep. In this way, Watcher can actively subscribe to any data changes.

The key here is that dependencies are collected when the getter for the data is executed

 Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: function reactiveGetter () {
      const value = getter ? getter.call(obj) : val
      if (Dep.target) {
        dep.depend() Collect dependencies in the getter
        if (childOb) {
          childOb.dep.depend()
          if (Array.isArray(value)) {
            dependArray(value)
          }
        }
      }
      return value
    },
    set: function reactiveSetter (newVal) {... }})Copy the code

For Watcher, this is as short as we can get, otherwise it would be too long.

The computed characteristics

Computed has two characteristics:

  • Computed does not run when it is not usedgetter
  • When you change the responsive data that computed depends on, you do not update the value of computed immediately; you have to wait until computed is used

Principles of the computed

Based on these two characteristics of computed tomography, we analyze it respectively

The first characteristic

During Vue initialization, the initComputed method is called in initState to initialize computed.

// ./src/core/instance/state.js
function initState(vm) {  // Initialize all states
  vm._watchers = []  // The current instance watcher collection
  const opts = vm.$options  // Merged attributes.// Initialize other states
  
  if(opts.computed) {  // If there are defined calculation attributes
    initComputed(vm, opts.computed)  // Initialize}... }Copy the code

So what does initComputed do

// ./src/core/instance/state.js
function initComputed(vm, computed) {
  const watchers = vm._computedWatchers = Object.create(null) // Create a pure object
  
  for(const key in computed) {
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    watchers[key] = new Watcher(
        vm,
        getter || noop,
        noop,
        {lazy: true})// Instantiate computedWatcher
    if(! (keyinvm)) { defineComputed(vm, key, userDef) } ... }}Copy the code

It checks whether computed is a function type or an object type. If it is a function, the function is the getter; otherwise, use computed. Get the getter to generate a Watcher and set the lazy property to true. This lazy attribute is intended to implement the first feature of computed.

// src/core/observer/watcher.js
class Watcher{
  constructor(vm, expOrFn, cb, options) {
    this.vm = vm
    this._watchers.push(this)
    
    if(options) {
      this.lazy = !! options.lazy// Indicates computed
    }
    
    this.dirty = this.lazy  // Dirty is the flag bit, indicating whether to use computed data
    
    this.getter = expOrFn  // Computed callback functions
    
    this.value = this.lazy
      ? undefined
      : this.get(); }}Copy the code

If lazy is true, the get method is not called directly, which is why getters for computed are not run when computed is not used.

Second characteristic

After a computed Watcher is generated, defineComputed continues.

function defineComputed(target, key, userDef) {...Object.defineProperty(target, key, {
    enumerable: true.configurable: true.get: createComputedGetter(key),
    set: userDef.set || noop
  })
}
Copy the code

Execute the defineComputed method to turn computed into responsive data through Object.defineProperty. In createComputedGetter, encapsulate the getter

function createComputedGetter (key) {
  return function () {  // Returns the getter function
    const watcher = this._computedWatchers && this._computedWatchers[key]
    // This can also be used to get computed-watcher for key
    
    if (watcher) {
      if (watcher.dirty) {  // True when watcher is instantiated, which means it needs to be evaluated
        watcher.evaluate()  // Evaluate the computed property, calling the computed. Get method passed
      }
      if (Dep.target) {  // The current watcher, here is the page render trigger this method, so render-watcher
        watcher.depend()  // Collect current watcher
      }
      return watcher.value  // Returns the evaluated value or the previously cached value}}} -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --class Watcher {... evaluate () {this.value = this.get()  // Evaluate attributes
    this.dirty = false  // Indicates that the calculated property has been calculated and no further calculation is required
  }
  depend () {
    let i = this.deps.length  // Deps is an array of dePs that calculate the responsive data accessible within the property
    while (i--) {
      this.deps[i].depend()  // let each DEP collect the current render-watcher}}}Copy the code

After using computed data, the getter method is called, and the Watcher corresponding to computed data is collected, so that computed properties change when the responsive data inside changes.

Now that we have covered the principles of computed computed, you may need to have a deeper understanding of vUE data response principles, such as Dep and Watcher, if you want to fully understand them.

Computed caching mechanism

Computed attributes also have a caching mechanism. Only when the responsive data it depends on changes, the cache will be cleared and the results will be recalculated. In fact, the above analysis already exists, that is, through the dirty attribute, the results will be recalculated and the cache will be replaced only when dirty is true. Dirty is set to true only when its responsive data delivery changes, and is set to false again after recalculation.

if (watcher.dirty) {  // True when watcher is instantiated, which means it needs to be evaluated
    watcher.evaluate()  // Evaluate the computed property, calling the computed. Get method passed
}
Copy the code

conclusion

Let’s summarize the above process.

  1. When Vue is initializedinitComputedTheta is generated in itlazyProperty of trueWatcher.WatcherAccording to thelazyProperty does not execute the GET method immediately.
  2. Then performdefineComputedBy means ofObject.definePropertyTurn computed into responsive data.
  3. In the use of thecomputedAfter that, the getter method is called,computedThe correspondingWatcherWill be collected, which is why after the responsive data in it changes,computedThe calculation properties change.