preface

This article is mainly handwritten Vue2.0 source code – computing attributes

In the last article, we mainly introduced the principle of Vue listening properties. We know how the user-defined Watch is created. In this article, we introduce his brother – computing properties

Applicable group:

1. Want to deeply understand vUE source code better for daily business development

2. Want to write in the resume proficient vUE framework source code (no longer afraid of the interviewer’s serial kill question haha)

3. No time to see the official source code or first look at the source code feel difficult to understand the students


The body of the

<script>
  // Instantiate Vue
  let vm = new Vue({
    el: "#app".data() {
      return {
        aa: 1.bb: 2.cc: 3}; },// render(h) {
    // return h('div',{id:'a'},'hello')
    // },
    template: ` < div id = "a" > hello, this is my own writing Vue {{computedName}} {{cc}} < / div > `.computed: {
      computedName() {
        return this.aa + this.bb; ,}}});// Rendering watcher executes a rendering watcher every time we change data. This affects performance
  setTimeout(() = > {
    vm.cc = 4;
  }, 2000);
  console.log(vm);
</script>
Copy the code

The above example is the basic use of the computed property. We changed cc in the template after two seconds, but the aa and BB that the computed property depends on have not changed, so the computed property will not be recalculated and will remain the result of the last calculation

1. Calculate the initialization of attributes

// src/state.js

function initComputed(vm) {
  const computed = vm.$options.computed;

  const watchers = (vm._computedWatchers = {}); // To store the computed watcher

  for (let k in computed) {
    const userDef = computed[k]; // Get the user-defined computed properties
    const getter = typeof userDef === "function" ? userDef : userDef.get; // Create computed properties for watcher to use
    // 创建计算watcher  lazy设置为true
    watchers[k] = new Watcher(vm, getter, () = > {}, { lazy: true}); defineComputed(vm, k, userDef); }}Copy the code

The evaluated property can be written as a function or it can be written as an object object so the get property represents the value of the evaluated property dependency and the set property represents the value of the evaluated property dependency and we care about the get property and then like the listen property we pass lazy:true to the Watcher constructor It’s used to create the computed property Watcher so what does defineComputed mean

Thinking about? The computed properties can cache the computed results what should we do?

2. The calculated attribute is hijacked

// src/state.js

// Define a normal object to hijack the computed properties
const sharedPropertyDefinition = {
  enumerable: true.configurable: true.get: () = > {},
  set: () = >{}};// Redefine the computed property to hijack get and set
function defineComputed(target, key, userDef) {
  if (typeof userDef === "function") {
    // If it is a function that needs to be manually assigned to get
    sharedPropertyDefinition.get = createComputedGetter(key);
  } else {
    sharedPropertyDefinition.get = createComputedGetter(key);
    sharedPropertyDefinition.set = userDef.set;
  }
  // Use object.defineProperty to hijack get and set of calculated properties
  Object.defineProperty(target, key, sharedPropertyDefinition);
}

// Override the get method that evaluates the property to see if it needs to be recalculated
function createComputedGetter(key) {
  return function () {
    const watcher = this._computedWatchers[key]; // Get the computation property watcher
    if (watcher) {
      if (watcher.dirty) {
        watcher.evaluate(); // When evaluating an attribute, re-evaluate it if it is dirty
      }
      returnwatcher.value; }}; }Copy the code

The defineComputed method basically redefines the computed attribute and the most important thing is to hijack the GET method which evaluates the attribute dependency. Why hijack it? Because we need to determine whether the computed attribute needs to be recalculated based on whether the dependency value has changed

The createComputedGetter method is the core of determining whether the value of the attribute dependent has changed. We add the dirty flag to the Watcher created by the attribute. If the flag becomes true, we need to call Watcher

3. The Watcher

// src/observer/watcher.js

// import { pushTarget, popTarget } from "./dep";
// import { queueWatcher } from "./scheduler";
// import {isObject} from '.. /util/index'
// // global variable id increals every time new Watcher is added
// let id = 0;

export default class Watcher {
  constructor(vm, exprOrFn, cb, options) {
    // this.vm = vm;
    // this.exprOrFn = exprOrFn;
    // this.cb = cb; Callback functions such as watcher can execute beforeUpdate methods before watcher updates
    // this.options = options; // The additional option true means render watcher
    // this.id = id++; // Unique identifier of watcher
    // this.deps = []; // The container where dep is stored
    // this.depsId = new Set(); // Used to remove dep
    // this.user = options.user; // Identify the user watcher
    this.lazy = options.lazy; // Identifies the computed property watcher
    this.dirty = this.lazy; // The dirty variable indicates whether the watcher needs to be recalculated. The default value is true

    // If the expression is a function
    // if (typeof exprOrFn === "function") {
    // this.getter = exprOrFn;
    // } else {
    // this.getter = function () {
    // // User watcher may send a string like A.A.A.B
    // let path = exprOrFn.split(".");
    // let obj = vm;
    // for (let i = 0; i < path.length; i++) {
    // obj = obj[path[i]]; //vm.a.a.a.a.b
    / /}
    // return obj;
    / /};
    // }
    // Get will be called by default for non-computed property instantiations
    this.value = this.lazy ? undefined : this.get();
  }
  get() {
    pushTarget(this); // Push the current watcher instance to the global dep.target before calling the method
    const res = this.getter.call(this.vm); // The computed property executes the user-defined get function here to access the dependencies of the computed property to add its own computed Watcher to the dep collection
    popTarget(); // Remove the current watcher instance from the global dep.target after calling the method
    return res;
  }
  // Put the dep in the deps and make sure that the same deP is only saved to watcher once and the same watcher is only saved to dep once
  // addDep(dep) {
  // let id = dep.id;
  // if (! this.depsId.has(id)) {
  // this.depsId.add(id);
  // this.deps.push(dep);
  // // directly calls dep's addSub method to add its watcher instance to dep's subs container
  // dep.addSub(this);
  / /}
  / /}
  // We simply execute the following get method and then we need to calculate the properties
  update() {
    // To change the value of the calculated attribute dependency, simply set dirty to true and recalculate it the next time you access it
    if (this.lazy) {
      this.dirty = true;
    } else {
      // Each time watcher makes an update, you can have them cached and then called together
      // Asynchronous queuing mechanism
      queueWatcher(this); }}// Recalculate the calculation property and set dirty to false when completed
  evaluate() {
    this.value = this.get();
    this.dirty = false;
  }
  depend() {
    // The watcher that calculates the property stores the deP of the dependency
    let i = this.deps.length;
    while (i--) {
      this.deps[i].depend(); // Call the dependency dep to collect the render watcher}}// run() {
  // const newVal = this.get(); / / the new values
  // const oldVal = this.value; / / the old values
  // this.value = newVal; // Then the old value becomes the current value
  // if (this.user) {
  // if(newVal! ==oldVal||isObject(newVal)){
  // this.cb.call(this.vm, newVal, oldVal);
  / /}
  // } else {
  // // Render watcher
  // this.cb.call(this.vm);
  / /}
  / /}
}
Copy the code

We’re going to focus on uncommented code and there are four major changes here

1. Do not call the GET method to access the value for dependency collection if the property is evaluated during instantiation

2. The update method sets the dirty id of the watcher to true and will only recalculate the property when it is accessed the next time

3. Add the evaluate method for calculating attribute recalculation

4. Added the Depend method to collect the outer watcher for the dependent value of the evaluated attribute — this method is very important to analyze next

4. Collect dependencies of the outer Watcher

// src/state.js

function createComputedGetter(key) {
// return function () {
// const watcher = this._computedWatchers[key]; // Get the computation property watcher
// if (watcher) {
// if (watcher.dirty) {
// watcher.evaluate(); // When evaluating an attribute, re-evaluate it if it is dirty
        if (Dep.target) {
    // If the Dep still has a target, then the data that the render watcher calculates the properties depends on will need to be collected
          watcher.depend()
        }
/ /}
// return watcher.value;
/ /}
/ /};
// }

Copy the code

This is where the watcher.depend method is important. If you think about it, when the value of the attribute that we calculate depends on has changed, then watcher’s dirty is true and the next time he accesses the attribute he does recalvaluate, but we don’t trigger the view update at all That is, the data changed and the view was not rerendered

Why is that?

Since the template only calculates the properties and the deP that calculates the properties’ dependencies only collects the watcher dependencies and the change itself only tells watcher to call update and set dirty to true, we need to find a way to render the computed properties’ dependencies as well Watcher’s dependency is to first notify compute Watcher to recalculate and then notify render Watcher to update the view when it changes itself

How do you do that? Let’s look at the following code to make it clear

// src/observer/dep.js

Dep.target is null by default
Dep.target = null;
// The stack structure is used to store watcher
const targetStack = [];

export function pushTarget(watcher) {
  targetStack.push(watcher);
  Dep.target = watcher; // dep. target points to the current watcher
}
export function popTarget() {
  targetStack.pop(); // The current watcher goes off the stack and gets the previous watcher
  Dep.target = targetStack[targetStack.length - 1];
}
Copy the code

So you can see that the container where the watcher is originally designed is a stack structure because throughout the Vue lifecycle there are a lot of Watcher, rendering watcher, calculating watcher, listening watcher and so on, and each Watcher is calling its own GET We call pushTarget and popTarget before and after the method. This way, when the evaluated property is recalculated, it immediately goes off the stack and the outer watcher becomes the new Dep. Target we use watcher.depend The watcher method collects the values of the computed property dependencies once in the outer render watcher so that when the values of the computed property dependencies change, the view can be recalculated and refreshed

For rendering update do not understand the students suggest to look at xiaobian this handwritten Vue2.0 source code (four) – rendering update principle

5. Calculate mind maps of attributes

summary

So far, Vue has completed the principle of evaluating attributes, which is still very different from listening attributes. Generally, evaluating attributes is used when the dependency needs to be evaluated and can be cached. When the dependency changes, the logic of automatically evaluating attributes is generally used in templates Listening for properties is to observe the value of a response (you can also observe the value of a calculated property) and once it changes, you can perform your own method. You can look at the mind map and write the core code yourself

Finally, if you find this article helpful, remember to like it three times. Thank you very much!

Series of links (will be updated later)

  • Handwriting Vue2.0 source code (a) – response data principle
  • Handwriting Vue2.0 source code (2) – template compilation principle
  • Handwriting Vue2.0 source code (three) – initial rendering principle
  • Handwriting Vue2.0 source code (four) – rendering update principle
  • Handwriting Vue2.0 source code (five) – asynchronous update principle
  • Handwriting Vue2.0 source code (six) -diff algorithm principle
  • Handwriting Vue2.0 source code (seven) -Mixin Mixin principle
  • Handwriting Vue2.0 source code (eight) – component principle
  • Handwriting Vue2.0 source code (nine) – listening attribute principle
  • Handwriting Vue2.0 source code (ten) – the principle of computing attributes
  • Handwriting Vue2.0 source code (eleven) – global API principle
  • The most complete Vue interview questions + detailed answers
  • Handwritten vue-Router source code
  • Write vuex source code
  • Handwriting vue3.0 source code

Shark brother front touch fish technology group

Welcome technical exchanges within the fish can be pushed for help – link