background

For a long time, I have little knowledge about watch and computed in VUE. (For example, the essence of watch and computed is new Watcher. Computed has cache, and it will only be executed when called, and only when the dependent data changes, will it be triggered again…) And then there was no more.

Also saw a lot of big guy to write the article, a large section of a large section of the source code listed, really let me see this dish chicken head big, natural also don’t want to see. Recently, I started to learn the vue source code and really understand how they are implemented.

data() {
  return {
    msg: 'hello guys'.info: {age:'18'},
    name: 'FinGet'}}Copy the code

watcher

What is a watcher? The listener? It’s just a class!

class Watcher{
  constructor(vm,exprOrFn,callback,options,isRenderWatcher){}}Copy the code
  • vmVue instance
  • exprOrFnIt could be a string or a callback (look behind you, it’s not important right now)
  • optionsVarious configuration items (configure what, look later)
  • isRenderWatcherWhether it isApply colours to a drawingWathcer

initState

An initState method is executed during Vue initialization, including the most familiar initData, object.defineProperty data hijacking.

export function initState(vm) {
  const opts = vm.$options;
  // Vue's data source property method data calculation property watch
  if(opts.props) {
    initProps(vm);
  }
  if(opts.methods) {
    initMethod(vm);
  }
  if(opts.data) {
    initData(vm);
  }
  if(opts.computed){
    initComputed(vm);
  }
  if(opts.watch) { initWatch(vm, opts.watch); }}Copy the code

During the data hijacking, Watcher’s best gay friend Dep shows up, and Dep is there to save Watcher.

function defineReactive(data, key, val) {
  let dep = new Dep(); 
  Object.defineProperty(data, key, {
    get(){
      if(Dep.target) {
        dep.depend(); // Collect dependencies
      }
      return val;
    },
    set(newVal) {
      if(newVal === val) return;
      val = newVal;
      dep.notify(); // Notification execution}})}Copy the code

When initData is used, dep. target is nothing, so the collection is lonely. Target is bound to the class Dep (static property), not to the instance.

But after $mount, things are different. $mount: compile, generate, render, patch, diff

There’s only one thing you need to know: the following code will be executed

new Watcher(vm, updateComponent, () = > {}, {}, true); // True indicates that it is a render watcher
Copy the code

UpdateComponent is an update, and regardless of how it’s executed, it’s now a callback that updates the page, and it’s stored in the getter of Watcher. It corresponds to the exprOrFn argument that we started with.

Hey, hey, hey, this time is different:

  1. Rendering a page is a call to the data you defined (don’t bar, defined not called), and will goget.
  2. new WatcherI’m going to call a method that puts this instance inDep.targetOn.
pushTarget(watcher) {
  Dep.target = watcher;
}
Copy the code

These two things come together, and dep.Depend () does the work.

So one thing you can understand from here is that all data defined in the data, whenever called, it will collect a render Watcher, that is, data changes, execute the dep.notify in the set and the render Watcher will be executed

Here is the definitionmsg,info,nameThree numbers, they all have oneApply colours to a drawingWatcher:

Sharp-eyed friends should have noticed that there are two watcher in MSG, one is a user-defined watch and the other is also a user-defined watch. Ah, of course not. Vue is de-weighted, there’s no repeat watcher, as you might expect, and computed Watcher;

User to watch

Here’s how we use watch:

watch: {
  msg(newVal, oldVal){
    console.log('my watch',newVal, oldVal)
  }
  // or
  msg: {
    handler(newVal, oldVal) {
      console.log('my watch',newVal, oldVal)
    },
    immediate: true}}Copy the code

InitWatch is executed, and after a short operation, exprOrFn(which is a string at this point), handler, and options are extracted, which somehow corresponds to Watcher, and the vm.$watch method is called.

 Vue.prototype.$watch = function(exprOrFn, cb, options = {}) {
    options.user = true; // mark it as user watcher
    // Core is to create a watcher
    const watcher = new Watcher(this, exprOrFn, cb, options);
    if(options.immediate){
      cb.call(vm,watcher.value)
    }
 }
Copy the code

Come on, you can’t help but look at this code (it was a long one, but to be honest, I cut out the ones that don’t have much to do with it) :

class Watcher{
  constructor(vm,exprOrFn,callback,options,isRenderWatcher){
    this.vm = vm;
    this.callback = callback;
    this.options = options;
    if(options) {
      this.user = !! options.user; }this.id = id ++;
    if (typeof exprOrFn == 'function') {
      this.getter = exprOrFn; // Place the internally passed callback function on the getter property
    } else {
      this.getter = parsePath(exprOrFn);
      if (!this.getter) {
        this.getter = (() = >{}); }}this.value = this.get();
  }
  get(){
    pushTarget(this); // Save the current watcher to deP
    let result = this.getter.call(this.vm, this.vm); // The execution of the render watcher goes to the observeGet method and saves the watcher
    popTarget(); By the time we get to this point, all the dependency collections are done, all with the same watcher
    returnresult; }}Copy the code
// 这个就是拿来把msg的值取到,取到的就是oldVal
function parsePath(path) {
  if(! path) {return
  }
  var segments = path.split('. ');
  return function(obj) {
    for (var i = 0; i < segments.length; i++) {
      if(! obj) {return }
      obj = obj[segments[i]];
    }
    return obj
  }
}
Copy the code

As you can see, new Watcher is going to do a get method, when it’s rendering Watcher is going to render the page, do updateComponent once, when it’s the user Watcher is going to do the return method in parsePath, And then you get a value this.value which is oldVal.

Dep. Depend () = dep. Depend ();

When MSG changes, there are some other operations involved, which don’t matter. Finally, a run method is executed, which calls the callback and passes in newValue and oldValue:

  run(){
    let oldValue = this.value;
    // Execute again to get the current value, will be rehash, Watcher does not repeat
    let newValue = this.get();
    this.value = newValue;
    if(this.user && oldValue ! = newValue) {// The user watcher calls callback
      this.callback(newValue, oldValue)
    }
  }
Copy the code

computed

computed: {
  c_msg() {
    return this.msg + 'computed'
  }
  // or
  c_msg: {
    get() {
      return this.msg + 'computed'
    },
    set(){}}},Copy the code

What are the characteristics of computed Tomography:

  1. Is executed when called
  2. Have a cache
  3. Recalculation occurs when dependencies change

When called, how do I know it’s called? DefineProperty, Object. DefineProperty.

Dependencies are recalculated when their data changes, which is where the dependencies need to be collected. Again, this. MSG -> get -> dep.depend() is called.

function initComputed(vm) {
  let computed = vm.$options.computed;
  const watchers = vm._computedWatchers = {};
  for(let key in computed) {
    const userDef = computed[key];
    // Get the get method
    const getter = typeof userDef === 'function' ? userDef : userDef.get;
    Watcher lazy is not called the first time
    watchers[key] = new Watcher(vm, userDef, () = > {}, { lazy: true });
    defineComputed(vm, key, userDef)
  }
}
Copy the code
const sharedPropertyDefinition = {
  enumerable: true.configurable: true.get: () = > {},
  set: () = >{}}function defineComputed(target, key, userDef) {
  if (typeof userDef === 'function') {
      sharedPropertyDefinition.get = createComputedGetter(key)
  } else {
      sharedPropertyDefinition.get = createComputedGetter(userDef.get);
      sharedPropertyDefinition.set = userDef.set;
  }
  // Use the defineProperty definition so that it can be used to calculate
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
Copy the code

DefineProperty = object.defineProperty = object.defineProperty

class Watcher{
  constructor(vm,exprOrFn,callback,options,isRenderWatcher){...this.dirty = this.lazy;
    // Lazy is not executed the first time
    this.value = this.lazy ? undefined : this.get(); . }update(){
    if (this.lazy) {
      // The calculated properties need to be updated
      this.dirty = true;
    } else if (this.sync) {
      this.run();
    } else {
      queueWatcher(this); // This is just a foil for now}}evaluate() {
    this.value = this.get();
    this.dirty = false; }}Copy the code

The cache is right there, so if you do get, you get a return value this.value is the cache value. In user Watcher, it’s oldValue. 🐂 🍺 plus!

function createComputedGetter(key) {
  return function computedGetter() {
    // This points to the vue instance
    const watcher = this._computedWatchers[key];
    if (watcher) {
      if (watcher.dirty) { // If dirty is true
        watcher.evaluate();// Evaluates the new value and updates dirty to false
      }
      // If the dependent value does not change, the result of the last calculation is returned
      return watcher.value
    }
  }
}
Copy the code

When is the update from Watcher called? When dep.notify() is called, dirty needs to be true. However, the calculation attributes still need to be calculated at the time of the call, so update only changes the status of dirty. And then it’s recalculated the next time it’s called.

class Dep {
  constructor() {
    this.id = id ++;
    this.subs = [];
  }
  addSub(watcher) {
    this.subs.push(watcher);
  }
  depend() {
    Dep.target.addDep(this);
  }
  notify() {
    this.subs.forEach(watcher= > watcher.update())
  }
}
Copy the code

conclusion

  1. watchcomputedNature isWatcher, are storedDepWhen data changes, it is executeddep.notifyPut the currentDepIn the instanceWatcherallrunSo let’s do thatApply colours to a drawingWatcherThe page refreshes;
  2. Each statistic has its ownDepIf it is called in the template, it must have oneRender the Watcher;
  3. initDataWhen, noWatcher2. Capable of being collected;
  4. Did you find any,Apply colours to a drawingWatcherComputed,exprOrFnIt’s all functions,The userWatcherAre all strings.

The code in the article is a brief version, there are a lot of details did not say, is not important for this article is not important, you can go to read the source code for a deeper understanding.