Source code analysis for vue computed

Before reading this article, it is recommended that you understand the vUE responsive principle and watch listener principles. You can take a look at the source code analysis VUE responsive Principle (see this if you can’t open it) and the source code analysis Vue Watch listener (see this if you can’t open it) to help you understand computed.

Usage examples

var vm = new Vue({
    el: '#app'.data: {
        message: 'Hello'
    },
    computed: {
        reversedMessage: function () {
            return this.message.split(' ').reverse().join(' ')},copyMessage: {
            get: function(){
                return this.message
            }
        }
    }
})
Copy the code

Find initComputed

In vue/ SRC /core/index.js, you can see import vue from ‘./instance/index’, importing vue.

In vue/SRC/core/instance/index, js,

import { initMixin } from './init'
/ /...

function Vue (options) {
  / /...
  this._init(options)
}

initMixin(Vue)
// ...

export default Vue
Copy the code

As you can see, Vue is a function method that calls an initialization method called _init and passes in the options argument. The file also executes the initMixin method.

In the vue/SRC/core/instance/init. Js,

// ...
import { initState } from './state'
import { extend, mergeOptions, formatComponentName } from '.. /util/index'

export function initMixin (Vue: Class<Component>) {
  Vue.prototype._init = function (options? :Object) {
    const vm: Component = this

    // ...

    // merge options
    if (options && options._isComponent) {
      // optimize internal component instantiation
      // since dynamic options merging is pretty slow, and none of the
      // internal component options needs special treatment.
      initInternalComponent(vm, options)
    } else {
      vm.$options = mergeOptions(
        resolveConstructorOptions(vm.constructor),
        options || {},
        vm
      )
    }
    // ...
    initState(vm)

    // ...}}Copy the code

See that the _init method is defined in the initMixin method. In the _init method, the constant VM is declared and the current instance is assigned, the options are accepted and processed, and the initState method is called.

In the vue/SRC/core/instance/state. Js,

import {
  set,
  del,
  observe,
  defineReactive,
  toggleObserving
} from '.. /observer/index'

export function initState (vm: Component) {
  // ...
  const opts = vm.$options
  // ...
  if (opts.computed) initComputed(vm, opts.computed)
  // ...
}
Copy the code

initComputed

const computedWatcherOptions = { lazy: true } // Lazy is true and watcher's dirty is true

function initComputed (vm: Component, computed: Object) {
  // Initialize a Watchers constant and define a _computedWatchers attribute on the vue instance pointing to the same empty object
  const watchers = vm._computedWatchers = Object.create(null)
  // Whether server render
  const isSSR = isServerRendering()

  // The for in loop gets each attribute of a computed object
  for (const key in computed) {
    // The value of a computed object property
    const userDef = computed[key]
    // The value can be either a function or an object with a get method that is assigned to the getter for calculating the result of the property
    const getter = typeof userDef === 'function' ? userDef : userDef.get
    // Non-production environment and getter does not exist warning
    if(process.env.NODE_ENV ! = ='production' && getter == null) {
      warn(
        `Getter is missing for computed property "${key}". `,
        vm
      )
    }

    // Non-server rendering creates an internal Watcher instance for each calculated property
    if(! isSSR) { watchers[key] =new Watcher(
        vm,
        getter || noop,
        noop,
        computedWatcherOptions
      )
    }

    // component-defined computed properties are already defined on the
    // component prototype. We only need to define computed properties defined
    // at instantiation here.
    // The calculated properties defined by the component already exist in the component prototype, so the calculated properties need to be defined at vUE instantiation.
    if(! (keyin vm)) {
      defineComputed(vm, key, userDef)
    } else if(process.env.NODE_ENV ! = ='production') {
      // In the non-production environment, perform attribute duplicate verification on data, props, and computed
      if (key in vm.$data) {
        warn(`The computed property "${key}" is already defined in data.`, vm)
      } else if (vm.$options.props && key in vm.$options.props) {
        warn(`The computed property "${key}" is already defined as a prop.`, vm)
      }
    }
  }
}
Copy the code

defineComputed

const sharedPropertyDefinition = {
  enumerable: true.configurable: true.get: noop,
  set: noop
}

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {
  // Caching is required for non-server rendering
  constshouldCache = ! isServerRendering()// What is done here is to complete an initialization property configuration for defineProperty based on the value of the calculated property passed in

  // To set get, the cache calls createComputedGetter to create a getter method
  if (typeof userDef === 'function') {
    sharedPropertyDefinition.get = shouldCache
      ? createComputedGetter(key)
      : createGetterInvoker(userDef)
    sharedPropertyDefinition.set = noop
  } else{ sharedPropertyDefinition.get = userDef.get ? shouldCache && userDef.cache ! = =false
        ? createComputedGetter(key)
        : createGetterInvoker(userDef.get)
      : noop
    sharedPropertyDefinition.set = userDef.set || noop
  }
  // Set the value of the calculation attribute
  if(process.env.NODE_ENV ! = ='production' &&
      sharedPropertyDefinition.set === noop) {
    sharedPropertyDefinition.set = function () {
      warn(
        `Computed property "${key}" was assigned to but it has no setter.`.this)}}// Define a compute property with the same name on the vue instance, passing in the configuration
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
Copy the code

createComputedGetter

function createComputedGetter (key) {
  // Returns a computedGetter method
  return function computedGetter () {
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // watcher. Dirty defaults to true, and evaluating changes dirty to false
      // When the set method is called, notify is triggered and reset to true by observing the update method on the value instance watcher
      if (watcher.dirty) { 
        watcher.evaluate() / / call the watcher example of evaluate method, triggering watcher instance the get method, is actually call the getter initComputed above,
      }
      // Dep.target has a value, then the Depend method of the watcher instance is called
      if (Dep.target) { // Dep.target is changed with pushTarget and popTarget methods, which keep dep. target pointing to the current Watcher when the get method of the watcher instance is called
        watcher.depend() // Collect dependencies
      }
      return watcher.value // Returns the value of the calculated property}}}Copy the code

conclusion

To review the source code analysis of the vUE responsive principle (if not open can see this knowledge) :

Our data object has an __ob__ attribute that corresponds to an Observer instance. The Observer instance overrides each attribute on data and stores the respective DEP array for each attribute through a closure. Each DEP array collects all Watcher Observer instances of that attribute. Each observer instance has its own DEPS dependency set, which collects the deP of the closure in reverse.

Suppose we have A vue instance vm with an A property on vm.data and A’ compute property on vm.puted with A value of function(){return this.a}.

So A produces an observer instance watcher A in the initData method, and A’ produces an observer instance watcher A’ in the initComputed method.

The first time vm.puted. A’ is called:

  1. Trigger A’ get, and since A’. Dirty defaults to true, call A’. Evaluate

  2. In a’. Evaluate, a’. Get is called

  3. In a’. Get, dep. target is set to a’, and a’. Getter is called, that is, function(){return this. a}, which triggers a.gate

  4. In a. et, depent calls dep. target (a’ addDep, a’. AddDep, a’. AddDep, which triggers dep.addSub.

Overall, this step completes the collection of both observers and dependencies

  1. Get, set dep. target to null, return to A ‘.evaluate, set a’.dirty to false

The second call to vm.puted. A’ returns the cached A’. Value because A’. Dirty is false.

When A is changed, the set of A is triggered and dep.notify is called A’. Update, where A’. Dirty is reset to true and A’. Evaluate is recalled the next time A’ is obtained.