preface

In the last article, the patch — DIff of Vue2 series realizes THE DOM diff process and completes the update of page responsive data.

The target

The goal of this article is to achieve computed attributes and complete the display of computed attributes in templates. Knowledge points involved:

  • Evaluate the nature of the property

  • Caching principles for calculating attributes

implementation

Now you start implementing computed attributes.

_init

/src/index.js

/** * Initializes the configuration object *@param {*} options 
 */
Vue.prototype._init = function (options) {
  // ...
  // Initialize options.data
  // Proxies individual properties on data objects to Vue instances
  // Set the responsiveness of each property on the data object
  initData(this)
  // Initialize computed options and proxy computed properties to Vue instances
  // Implement caching with Watcher
  initComputed(this)
  // Install the renderer function at runtime
  renderHelper(this)
  // ...
}

Copy the code

initComputed

/src/initComputed.js

/** * Initialize computed configuration items * Instantiate a Watcher for each item, And proxy its computed properties to Vue instances * For computed caching in combination with watcher.dirty and watcher.evalute *@param {*} Vm Vue instance */
export default function initComputed(vm) {
  // Get computed configuration items
  const computed = vm.$options.computed
  / / record watcher
  const watcher = vm._watcher = Object.create(null)
  // Iterate over a computed object
  for (let key in computed) {
    // Instantiate Watcher, the callback function executes lazily by default
    watcher[key] = new Watcher(computed[key], { lazy: true }, vm)
    // Proxy the computed attribute key to the Vue instance
    defineComputed(vm, key)
  }
}

Copy the code

defineComputed

/src/initComputed.js

/** * Proxies calculated properties to Vue instances *@param {*} Vm Vue instance *@param {*} Computed attributes */ for key computed
function defineComputed(vm, key) {
  // Attribute descriptor
  const descriptor = {
    get: function () {
      const watcher = vm._watcher[key]
      if (watcher.dirty) { // Indicates that the current computed callback has not been executed in this rendering cycle
        // Execute evalute to tell Watcher to run a computed callback function and get the value returned by the callback function
        watcher.evalute()
      }
      return watcher.value
    },
    set: function () {
      console.log('no setter')}}// Proxy calculated properties to Vue instances
  Object.defineProperty(vm, key, descriptor)
}

Copy the code

Watcher

/src/watcher.js

/ * * *@param {*} Cb callback function, responsible for updating the DOM callback function *@param {*} Options Watcher configuration item */
export default function Watcher(cb, options = {}, vm = null) {
  // Back up the CB function
  this._cb = cb
  // The value after the callback function is executed
  this.value = null
  // Computed attributes implement caching by marking whether the current callback function has been executed in the current rendering cycle
  this.dirty = !! options.lazy/ / the Vue instance
  this.vm = vm
  // The cb function is executed directly, and the cb function will read the attributes of vm.xx for dependency collection! options.lazy &&this.get()
}

Copy the code

watcher.get

/src/watcher.js

/** * is responsible for executing Watcher's CB function * when executing dependency collection */
Watcher.prototype.get = function () {
  pushTarget(this)
  this.value = this._cb.apply(this.vm)
  popTarget()
}

Copy the code

watcher.update

/src/watcher.js

/** * DeP tells Watcher to update the DOM */ using the this._cb function
Watcher.prototype.update = function () {
  // With Promise, place the execution of this._cb after this.dirty = true
  // Otherwise, the first computation of a computed property will fail when the button is clicked,
  // Because this._cb will update the component when it executes, this.dirty will still be this.dirty when it gets the value of the calculated property
  // The last value was false, so that the latest value of the calculated property could not be obtained
  // This is not necessary with asynchronous update queues, since asynchronous update objects are essentially Promises
  Promise.resolve().then(() = > {
    this._cb()
  })
  // After _cb is executed, DOM is updated and the next rendering cycle is started, so set dirty to false
  // The evalute method can be reexecuted to get the latest value when the calculated property is retrieved again
  this.dirty = true
}

Copy the code

watcher.evalute

/src/watcher.js

Watcher.prototype.evalute = function () {
  // Execute get, triggering the calculation function (cb) to execute
  this.get()
  // Set dirty to false for computed caching during a refresh cycle
  this.dirty = false
}

Copy the code

pushTarget

/src/dep.js

// Store all dep.targets
// Why are there multiple dep.targets?
// The component will generate a render Watcher. During the render process, if the user Watcher is processed,
// For example, for computed attributes, evalute -> get is executed
// If you assign dep. target directly, the previous value of dep. target -- render Watcher -- is lost
// Causes reactive data rendered after computed attributes to fail to complete dependency collection
const targetStack = []

/** * Back up the passed Watcher and assign it to dep.target *@param {*} Target Watcher instance */
export function pushTarget(target) {
  // Back up the Watcher passed in
  targetStack.push(target)
  Dep.target = target
}

Copy the code

popTarget

/src/dep.js

/** * resets dep. target to the previous Watcher or null */
export function popTarget() {
  targetStack.pop()
  Dep.target = targetStack[targetStack.length - 1]}Copy the code

The results of

Ok, so here you are, with the Vue computed property implementation, and if you can see this, everything is fine.

As you can see, the computed properties in the page are displayed properly, and you can update in response, with caching capability (see computed output through the console).

This leaves the final piece of the Handwritten Vue series — the asynchronous update queue of the Handwritten Vue series.

Focus on

Welcome everyone to pay attention to my nuggets account and B station, if the content to help you, welcome everyone to like, collect + attention

link

  • Proficient in Vue stack source code principles

  • Form a complete set of video

  • Learning exchange group