What are the differences between computed and Watch

Similarities:

  • Calculate attributeandMonitor propertiesBoth are essentially an instance of Watcher, and they all passResponsive systemEstablish communication with data and pages.

The difference between

  • Compute properties have “lazy computing” function. why didn’t I say cache? You’ll see.

  • The logic of listening is different. This is especially obvious from use, when the listener property is the target value changed and it executes the function. And the evaluated property is that the value of the function changes, it reevaluates.

  • The calculated properties are executed immediately after the page is refreshed by default, whereas the Watch property needs to be configured ourselves

In fact, on the surface, there seems to be only so much difference between calculated and listened properties. But, you know, that’s not enough. So we’re going to dig into these two points at the source level, and figure out the next difference between them: implementation differences.

computed

Basically, what it does is, it automatically evaluates the formulas that we define in the function.

data() {
    return {
        a: 1.b: 1}}computed: {
    total() {
        return this.a + this.b
    }
}
Copy the code

If the value of this.a or this.b changes, the total value will change. That’s what we need to figure out.

implementation

From this function name, you can see that initComputed is a function that initializes computed properties. All it does is run through the computed objects we’ve defined, and then define a Watcher instance for each value.

The Watcher instance is the role in a responsive system that listens for changes in data. If you are not familiar with Vue2’s responsive system, I suggest you read this article to talk about the principles of Vue2’s responsive system in plain English.

This. A and this.b. These two values are defined as responsive Data when Data is initialized. They’ll have an internal Dep instance, and the Dep instance will put the calculated watcher in its sub array. When you update yourself later, notify the watcher instance in the array to update.

const computedWatcherOptions = { lazy: true }

// VM: Component instance computed computed property objects in the component
function initComputed (vm: Component, computed: Object) {
  // Iterate over all computed attributes
  for (const key in computed) {
    // User-defined computed
    const userDef = computed[key]
    const getter = typeof userDef === 'function' ? userDef : userDef.get

    watchers[key] = new Watcher( / / 👈 here
      vm,
      getter || noop,
      noop,
      computedWatcherOptions
    )
 
  defineComputed(vm, key, userDef)
}
Copy the code

ComputedWatcherOptions, which passes {lazy: true}, means that the watcher instance will not execute the calculated property function immediately after it is created. This is also different from listening properties, which we will see later

At this point we see the role of calculating watcher instances in calculating the property execution flow, which is the initialization process. Now let’s look at how the evaluated properties are performed.

If you look closely at the top code, there is a defineComputed at the bottom, which defines a calculated property. It does two things internally, takes the function we defined (to keep the process simple, we’ll just look at the DEMO definition), and stuffs object.defineProperty. So that when we visit, we can execute the function that we’ve defined.

export function defineComputed (
  target: any,
  key: string,
  userDef: Object | Function
) {

  sharedPropertyDefinition.get = computedGetter

  // A sharedPropertyDefinition is triggered when the key of the computed property is called once
  // Did a hijacking of Computed
  Object.defineProperty(target, key, sharedPropertyDefinition)
}
Copy the code

Target is just this. So every time we use a computed property, we’re going to execute a computedGetter just like we did in our DEMO, this.total, it’s going to execute the function that we defined. How do you do that?

function computedGetter () {
    // Get the watcher instance created above
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // When first executed, dirty is true because it is based on lazy
      if (watcher.dirty) {
        // This method performs a calculation
        // Set dirty to false
        // After this function completes, the current calculation watcher will derive
        watcher.evaluate()
      }
      // If the currently active render Watcher exists
      if (Dep.target) {
        /** * evaluate: if the current render Watcher exists, the deP of the current collection will be notified to collect the current render Watcher. * If the calculated property is used in the template and the render Watcher is not collected by the corresponding DEP * then the calculated property dependency value changes when the update is issued and the current render Watcher is not updated *. The value of a calculated property in a page has not changed. * * Essentially the deP on which the calculated property depends, and can also be viewed as an instance of the property value itself. */
        watcher.depend()
      }
      return watcher.value
    }
  }
Copy the code

This function is the core logic for evaluating property implementations. I added a lot of notes. I hope they’re useful. Let’s go back to the first difference mentioned at the beginning of this article.

Why am I saying that the calculated property is lazy computing? The key is the watcher. Dirty in the code above. At first, watcher. Dirty will be set to true.

Watcher evaluate() is performed. The get here, you just have to think of it as the function that we defined to calculate the property.

evaluate () {
    this.value = this.get()
    this.dirty = false
  }
Copy the code

This.dirty is then changed to false

🤔 that is, since it is false, the function will not be executed in the future. Why would Vue do that? Of course, it depends on the value does not change, there is no need to calculate

The next question we need to consider is when dirty will be true again, obviously when it needs to be recalculated.

Here we need to look at the code for a reactive system. Here we just need to look at the logic of the set part

set: function reactiveSetter (newVal) {
  const value = getter ? getter.call(obj) : val
  
  if(newVal === value || (newVal ! == newVal && value ! == value)) {return
  }

  // Notify its subscribers of updates
  dep.notify()
}
Copy the code

This code does two things:

  • If the new value is the same as the old value, we don’t have to do anything.
  • The subscriber under this data is notified of the watcher instance update.

Notify simply iterates through its array and executes the Update method for each watcher in that array

update () {
    /* istanbul ignore else */
    if (this.lazy) {
      // Assume that the current publisher notification value is reset
      // Set dirty to true so that computations can be invoked again when computed is used
      // After rendering wacher completes heap, it is the turn of the current rendering Watcher to perform update
      QueueWatcher (this) is executed and the component rendering time is re-executed
      // The calculation property is used, in which case dirty is true so it can be reevaluated
      // The dirty is like a valve to determine if the calculation should be recalculated
      this.dirty = true}}Copy the code

This is where dirty is reset to true. Let’s summarize the dirty process

First, dirty is set to true at first, and is set to false once a calculation has been performed. Then when it defines the internal dependency values of functions such as this.a and this.b to sound different. This value will return to true.

Now we know what dirty does. It is used to record whether the value we depend on has changed, and if it has changed, it recalculates the value, and if it has not changed, it returns the previous value. It’s like a lazy loading concept. This is also a way of calculating the property cache, is the implementation logic completely irrelevant to the cache?


You might be surprised at this point. Until here, we have been make dirty only true | false, no involves calculating the execution of the attribute function?

So now we’re going to look at when the computed property gets executed, so let’s go back and look at the computedGetter, okay

function computedGetter () {
    // Get the watcher instance created above
    const watcher = this._computedWatchers && this._computedWatchers[key]
    if (watcher) {
      // When first executed, dirty is true because it is based on lazy
      if (watcher.dirty) {
        // This method performs a calculation
        // Set dirty to false
        // After this function completes, the current calculation watcher will derive
        watcher.evaluate()
      }
      // If the currently active render Watcher exists
      if (Dep.target) {
        /** * evaluate: if the current render Watcher exists, the deP of the current collection will be notified to collect the current render Watcher. * If the calculated property is used in the template and the render Watcher is not collected by the corresponding DEP * then the calculated property dependency value changes when the update is issued and the current render Watcher is not updated *. The value of a calculated property in a page has not changed. * * Essentially the deP on which the calculated property depends, and can also be viewed as an instance of the property value itself. */
        watcher.depend()
      }
      return watcher.value
    }
  }
Copy the code

There is a dep.target logic here. What does that mean? Dep.target is the currently rendering component. It refers to the component that you define, and it’s also a Watcher, which we call a rendering Watcher.

Evaluate the property watcher, which changes the value of dirty when notified of updates. When the rendering Watcher is notified of an update, it updates the page once.

The problem is, of course, that the dirty of calculating attributes is true again, so how do we tell the page that it’s being refreshed?

The Dep instance of the current data is notified to collect our render Watcher via watcher.depend(). When data changes, calculate Watcher is notified to change the drity value, and render Watcher is notified to update the page. When rendering watcher updates the page, if we use the total attribute in the HTML result of the page. It fires its corresponding computedGetter method. That is, execute the code above. When drity is true, execute the Watcher.evaluate () method as planned

Does it re-collect this render Watcher? Don’t worry about it. It’s up to you to figure out how to deal with it.

So now that we’re done with computed logic, it doesn’t seem that hard, right? Let’s sum it up:

  • Caching with a computed attribute actually works by using a dirty field as a choke valve that opens if it needs to be reevaluated, otherwise it keeps returning to the original value without recalculating.

  • Computed properties, like components, are essentially a Watcher instance.

watch

Watch is much simpler. Here we skip all the configurations of watch property and only consider its basic functions

Let’s start with the Demo, which is the simplest example, where whenever count changes, the handler function gets executed. That’s what we need to think about right now.

data() {
    return {
        count: 0}},watch: {
    count: {
        hanlder(){
            console.log('count changed')}}}Copy the code

Looking directly at the source code, in the initialization state, there is an initWatch function, which initializes our listening properties

implementation

// src/core/instance/state.js
function initWatch (vm: Component, watch: Object) {
  // Iterate over the wathcer we defined
  for (const key in watch) {
    const handler = watch[key]
    if (Array.isArray(handler)) {
      for (let i = 0; i < handler.length; i++) {
        createWatcher(vm, key, handler[i])
      }
    } else {
      createWatcher(vm, key, handler)
    }
  }
}
Copy the code

This function will get the count object defined in our watch object, and then get the handler value, as we can see from the above code, handler is actually an array and ok.

Let’s move on to the createWatcher function, which will parse out our configuration and then call $watch to listen. In fact, we can also use this method to do a functional listening

function createWatcher (
  vm: Component,
  expOrFn: string | Function, handler: any, options? :Object
) {
  if (isPlainObject(handler)) {
    options = handler
    handler = handler.handler
  }
  if (typeof handler === 'string') {
    handler = vm[handler]
  }
  return vm.$watch(expOrFn, handler, options)
}
Copy the code

The $watch function is a method on the prototype of the Vue instance, which is why we can call it as this

Vue.prototype.$watch = function (
    expOrFn: string | Function.// This can be key
    cb: any, // The function to be executedoptions? :Object // Some configuration
  ) :Function {
    const vm: Component = this
    // Create a watcher expOrFn at this time is the expOrFn listener
    const watcher = new Watcher(vm, expOrFn, cb, options)
    
    return function unwatchFn () {
      watcher.teardown()
    }
  }
Copy the code

As you can see from the code, the $watch property actually instantiates a Watcher object, and then implements the listener through that Watcher, which is the same as evaluating the property.

Since it is also an instance of Watcherh, it is essentially all listening through Vue’s responsive system. So what we need to think about is the Dep instance of count, when was this Watcher instance collected

Let’s look at some of the arguments passed to the Watcher constructor during instantiation.

  • vmIs a component instance, which is what we usually usethis
  • expOrFnIt’s in ourDemoIs in thecountThat is, the properties being listened on
  • cbIt’s oursThe handler function
if (typeof expOrFn === 'function') {
  this.getter = expOrFn
} else {
  // If it is a character, it is converted to a getter
  // We do this to pass this.[watcherKey]
  // Can trigger dependency collection for monitored attributes
  this.getter = parsePath(expOrFn)
  if (!this.getter) {
    this.getter = noop process.env.NODE_ENV ! = ='production' && warn(
      `Failed watching path: "${expOrFn}"` +
      'Watcher only accepts simple dot-delimited paths. ' +
      'For full control, use a function instead.',
      vm
    )
  }
}
this.value = this.lazy
  ? undefined
  : this.get()
Copy the code

This is the default string of code that watcher executes when it instantiates, remember the function we passed in for computed instantiation, also expOrFn. If it’s a function it’s going to be given directly. If it’s a string. ParsePath is created as a function. Note: this.[expOrFn]. Note: this.[expOrFn]. Is this the total

Finally, because lazy is false, the value will only be passed true if the property is evaluated. So this.get() is executed for the first time. Inside get, a getter() is executed to trigger the response

At this point, the initialization logic for the listening properties is complete, but there is a difference between the firing of the listening properties and the calculation properties when the data is updated.

Listen properties are triggered asynchronously. Why?

  • In fact, the execution logic of the listening property is the same as the component’s rendering. They all go into onenextTickFunction, yes, we are familiar with the API. It allows us to synchronize logic, in the next Tick execution.

If you don’t understand the logic of nextTick, that’s fine. Follow me, and I’ll focus on this API in my next article

conclusion

🤭 I’m sure you know how to answer this question. (I won’t mention application differences here, you know better than I do…)

Similarities:

  • Compute and listen properties, as well as component instances, are essentially a Watcher instance. It’s just different behavior.

  • Neither the evaluate nor the listen properties make any changes to assignments where the new value is the same as the old value. But this is done by responsive systems

Difference:

  • The calculated property has the “lazy calculation” feature, allowing recalculation only if the dependent value changes. It’s called “caching “, which I personally feel is inaccurate

  • When data is updated, the dirty status of calculated properties changes immediately, while listening properties and components are re-rendered with at least the next “tick”.

Thank 😘


If you find the content helpful:

  • ❤️ welcome to focus on praise oh! I will do my best to produce high-quality articles

Contact author: Linkcyd 😁 Previous:

  • That’s all you need to know about regular expressions
  • React Get started with 6 brain maps
  • Interviewer: Come on, hand write a promise
  • Prototype and Prototype Chain: How to implement Call, Bind, New yourself
  • That’s all you need to know about regular expressions