I was tired of interviewing recently, but I finally came to an end

preface

Observe, Observer and defineReactive for Vue’s MVVM mode

Vue’s MVVM mode responsivity principle — The special processing of array is not to change the column

The content of this chapter is mainly connected with the above two, interested in the first look ~

  • The old rule is the conclusion and the picture first

conclusion

  1. New Watcher(): is an entry function that needs to specify objects, expressions, and callbacks; Trigger a callback to the response when the expression changes;
  2. Get (): When reading reactive data, collect the dependency, which is an instance of Watcher;
  3. Set (): When setting reactive data, the notification depends on the notify of a method on the Watcher instance

1. In the template parsing stage, read the value of obj. A when parsing {{obj. 2. When using watch API;

  • The flow chart

Now that all levels of data are already responsive.

So how do you notify all the components that use data when it changes

【 Topic 】 Recall the use of watch in Vue, the principle of which is explained in this chapter.

Watch type: {[key: string] : string | Function | Object | Array} in detail: an Object, the key is to observe the expression of that value is corresponding to the callback Function. The value can also be a method name, or an object that contains options. The Vue instance will call $watch() at instantiation time, iterating through each property of the Watch object. { data: { a: 1, }, watch:{ a: Function (val, oldVal) {console.log('a ', oldVal)},} /* / data.a = 2 /* console */ 'a ', 2, 1}Copy the code

Look at wacht in action and think about how it is implemented internally. Here's a question to think about, and now to answer it step by step.

First, Vue uses a publish and subscribe model, collecting dependencies when reading data and notifying updates when changing data

export default function defineReactive(obj, key, value) {
  let childOb = observe(value)
+ let dep = new Dep()
  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get(){+if (Dep.target) {
+       dep.depend()
+     }
    }
      console.log('Access data triggers get' + 'You are trying to access' + key + 'properties', value)
      return value
    },
    set(newVal) {
      if (newVal === value) {
        return
      }
      console.log('Modify data to trigger set' + 'You are trying to modify' + key + 'properties', newVal)
      value = newVal
+     dep.notify()
    }
  })
}
Copy the code

Add this logic to get and set. And the collection dependency is found only if dep.target exists.

Dep and Watcher — who collects the dependencies and who the dependencies are;

Dep depends on the collector

Dep is a class that collects dependencies from instances of the Dep class; You can see that there is a DEP for each of the reactive attributes

let uid = 0;
/** * dep. target is a global flag that can only handle one watcher at a time in Vue
Dep.target = null;
/ * * *@this.id Each Dep instance has an ID that identifies itself *@this.subs Each Dep instance has an array of subs to store watcher * */
function Dep() {
  this.id = uid++;
  this.subs = [];
}
/** * addSub adds watcher to deP; * called by an instance of Dep@sub Example of Watcher * */
Dep.prototype.addSub = function (sub) {
  console.log("addSub");
  this.subs.push(sub);
};
/** * Add dep to watcher by calling * depend in get(); * * /
Dep.prototype.depend = function () {
  console.log("Collect dependencies in the getter,depend");
  Dep.target.addDep(this);
};
/** * Call the update method of each watcher in subs. Component Render Function * */
Dep.prototype.notify = function () {
  console.log("notify");
  const subs = this.subs.slice();
  for (let i = 0, l = subs.length; i < l; i++) { subs[i].update(); }};export { Dep };

Copy the code

Watcher dependencies (subscribers)

Watcher is a class whose instance is a dependency. It is also called a subscriber because it subscribes to some data, such as {{data.a}}, which is a subscription.

import { Dep } from "./Dep";

/ * * *@uid2 is an external variable that identifies the ID * */ of each watcher
let uid2 = 0;

function Watcher(vm, exp, cb) {
  this.vm = vm; / / 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴 vm in this article means the data 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴
  this.exp = exp; / / 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴 exp expression Such as data. A 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴
  this.cb = cb; / / 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴 cb callback function The change in the value of the exp call 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴
  this.id = uid2++;
  this.depIdsAnddeps = {};
  this.getter = parsePath(this.exp);
  this.value = this.get(); 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴 every timenewWhen the Watcher will touch 🔴 🔴 🔴 🔴 🔴 🔴 🔴 🔴} Watcher. Prototype. Update =function () {
  /** * the update method performs the cb callback when the view changes. This method actually triggers the view change ** /
  let value = this.get();
  let oldValue = this.value
  this.cb.call(this,value,oldValue);
};
/** * Add dep to watcher ** /
Watcher.prototype.addDep = function (dep) {
  if (!this.depIdsAnddeps.hasOwnProperty(dep.id)) {
    console.log("addDep");
    this.depIdsAnddeps[dep.id] = dep;
    dep.addSub(this); }}; Watcher.prototype.get =function () {
  let vm = this.vm;
  Dep.target = this;
  console.log("Dep.target=", Dep.target);
  let value = this.getter.call(this, vm);
  Dep.target = null;
  console.log("Touch end dep. target=", Dep.target);
  return value;
};

Copy the code

Three,Dep.targetMake each Watcher correspond exactly to the DEP

  • If you look closely at the code above, you can see that if you don’t do new Watcher() then there are no dependencies to collect. Dep. Target is always null, which makes it impossible to get by if.

  • When the this.get() method is triggered after new Watcher(), the global variable dep.target is first assigned to an instance of the current Watcher. The if judgment passes, and dep. Depend collects the watcher

  • When set() is triggered, the property value is accessed internally again, so get() is triggered again, and with this if judgment, double collection is avoided.

Let’s make a hypothesis

1. Trigger get() with touch data

  1. Now all of our data is responsive.

  2. At this point, Vue is internally parsing the template, so it must have parsed something like this. {{data.a}} or {{data.b}} etc..

  3. New Watcher(vm, expression data.a,cb callback function..) is executed internally. Or new Watcher(vm, expression data.b,cb callback function..)

  4. Read data.a and data.b. This triggers the get() of data.a and data.b to establish a corresponding relationship.

  • Use a picture to illustrate the process

Set ();

  1. {{data.a}} deP and Watcher have established a relationship

  2. Data. A = 2

  3. When set() is fired, data.a’s DEP calls notify(), which iterates through all the watcher stored in the DEP and calls the update method.

  4. The bound callback Function is fired, the Component Render Function, and the view is updated.

(This part involves Compile, you can bind one manually to make it easier to understand)

new Watcher(obj, "data.a".function () {
  console.log("Component" + "Render Function" + "View update callback");
});
Copy the code

Console output => “Component” + “Render Function” + “view update callback”, 2, 1

  • Use a picture to illustrate the process

5. Now go back to the picture at the beginning

Back to Vue, watch is just a manual new Watcher(), passing in the specified expression and callback. The usage is consistent with the examples in this article.

This article source in my GitHub repository, welcome to visit. mvvm-webpack-demo