This is the sixth day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

preface

Having looked at the implementation of the Watcher constructor in the previous article, this article will take a closer look at some of the functions defined internally by Watcher.

First, take a look at the two functions related to Dep, addDep and update

addDep

The Depend method is called when a dependency is added to the Dep, inside which Watcher’s addDep is called.

depend () {
  if (Dep.target) {
    Dep.target.addDep(this)}}Copy the code

Take a look at the addDep method:

addDep (dep: Dep) {
  const id = dep.id
  if (!this.newDepIds.has(id)) {
    this.newDepIds.add(id)
    this.newDeps.push(dep)
    if (!this.depIds.has(id)) {
      dep.addSub(this)}}}Copy the code

The addDep function does two main things:

  • Add the Dep you want to subscribe to to the newDeps array and add its ID to the newDepIds
  • Add the current Watcher to the DEP

Before adding a dependency, check whether it has been added to avoid repeated adding.

update

The notify method of the Dep is triggered when dependent updates are made. It calls Watcher’s update method internally.

notify () {
    ...
    
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
Copy the code

Look at the Update function of the Watcher class:

update () {
  /* istanbul ignore else */
  if (this.lazy) {
    this.dirty = true
  } else if (this.sync) {
    this.run()
  } else {
    queueWatcher(this)}}Copy the code
  • First, the lazy value is evaluated. If it is true, it is computed Watcher. Set dirty to true and do not count.

  • Then check the value of sync. If true, perform watcher’s callback on the current tick.

  • Otherwise, the current Watcher is pushed to the Watcher queue. More on queueWatcher later.

run

Let’s look at an implementation of the run function:

run () {
  if (this.active) {
    const value = this.get()
    if( value ! = =this.value ||
      // Deep watchers and watchers on Object/Arrays should fire even
      // when the value is the same, because the value may
      // have mutated.
      isObject(value) ||
      this.deep
    ) {
      // set new value
      const oldValue = this.value
      this.value = value
      if (this.user) {
        try {
          this.cb.call(this.vm, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "The ${this.expression}"`)}}else {
        this.cb.call(this.vm, value, oldValue)
      }
    }
  }
}
Copy the code
  • First determine the value of active to ensure that the current Watcher is still alive
  • Then call get to get the current value of Watcher
  • Next, determine whether the execution condition (value! = = this. Value | | isObject (value) | | this. Deep), if meet the execution cb callback function.

depend

The Depend function is used to add the current Watcher to all the DEPs on which it depends.

depend () {
  let i = this.deps.length
  while (i--) {
    this.deps[i].depend()
  }
}
Copy the code

teardown

The teardown function, as opposed to the Depend function, removes the current Watcher from all the DEPs to which it subscribes.

teardown () {
    if (this.active) {
      // remove self from vm's watcher list
      // this is a somewhat expensive operation so we skip it
      // if the vm is being destroyed.
      if (!this.vm._isBeingDestroyed) {
        remove(this.vm._watchers, this)}let i = this.deps.length
      while (i--) {
        this.deps[i].removeSub(this)}this.active = false}}}Copy the code

evaluate

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

The evaluate function is primarily used to be invoked by computed Watcher.

Computed Watcher is not evaluated during initialization; it is simply assigned to undefined and the dirty attribute is set to true.

When computed Watcher really needs to be evaluated, the evaluate function is called and dirty is set to false (indicating that the data is now clean and up to date).

Dep and Watcher

Here we can see that Dep and Watcher have a many-to-many relationship. The SUBs array of Dep stores the collected Watcher dependencies, while the DEPS array of Watcher also stores the Dep instances that Watcher subscribes to.

When Object attributes are intercepted by Object.defineProperty, each attribute has its own Dep instance that holds all the associated Watcher for that attribute.

Look at a simple example:

<template>
    <div>
        <span>{{firstName}}</span>
        <p>{{userName}}</p>
    </div>
</template>

<script>
export default {
    data() {
        return{
            firstName: 'first'.lastName: 'last'}},computed: {
        userName() {
            return this.firstName + this.lastName
        }
    },
    watch: {
        'userName'(newVal){
            console.log(newVal)
        }
    }
}
</script>
Copy the code

Take a look at the Watcher list:

Here are three Watcher’s:

  • computed Watcher(expression: “function userName() { return this.firstName + ‘-‘ + this.lastName; } “)
  • user Watcher(expression: “userName”)
  • render Watcher(expression: “function (){vm._update(vm._render(), hydrating); } “)

Dep(1209) vs. Dep(1210) :

In general, I think you can understand the many-to-many relationship this way: