Re-learn Vue source code, according to Huang Yi big man Vue technology revealed, one by one, consolidate the Vue source knowledge points, after all, chewed up is their own, all the articles are synchronized in the public number (road in the front stack) and github.

The body of the

When I call a property defined in obj in defineRective, I fire its getter. When I call a property defined in obj, I collect the render watcher as a dependency. The setter is triggered when the data is changed:

Object.defineProperty(obj, key, {
  enumerable: true.configurable: true.// ...
  set: function reactiveSetter (newVal) {
    const value = getter ? getter.call(obj) : val
    /* eslint-disable no-self-compare */
    if(newVal === value || (newVal ! == newVal && value ! == value)) {return
    }
    /* eslint-enable no-self-compare */
    if(process.env.NODE_ENV ! = ='production' && customSetter) {
      customSetter()
    }
    if (setter) {
      setter.call(obj, newVal)
    } else{ val = newVal } childOb = ! shallow && observe(newVal) dep.notify() } })Copy the code

Let’s see what the setter does:

First, compare the new value with the old value. If they are the same, do nothing. Otherwise, do some logic. Shallow && observe(newVal). If the new value neVal is also an object, observe once to become a responsive object. The other is dep.notify(), which notifies all subscribers, the process of sending out updates.

Distributing process

Take a look at notify() in SRC /core/observer/dep.js:

class Dep {
  // ...
  notify () {
  // stabilize the subscriber list first
    const subs = this.subs.slice()
    for (let i = 0, l = subs.length; i < l; i++) {
      subs[i].update()
    }
  }
}
Copy the code

Logic is simpler, traverse all the subs, namely Watcher instance array, then notify them, call each Watcher of the update method, update defined in SRC/core/observer/Watcher. In js:

class Watcher {
  // ...
  update () {
    /* istanbul ignore else */
    if (this.computed) {
      // A computed property watcher has two modes: lazy and activated.
      // It initializes as lazy by default, and only becomes activated when
      // it is depended on by at least one subscriber, which is typically
      // another computed property or a component's render function.
      if (this.dep.subs.length === 0) {
        // In lazy mode, we don't want to perform computations until necessary,
        // so we simply mark the watcher as dirty. The actual computation is
        // performed just-in-time in this.evaluate() when the computed property
        // is accessed.
        this.dirty = true
      } else {
        // In activated mode, we want to proactively perform the computation
        // but only notify our subscribers when the value has indeed changed.
        this.getAndInvoke(() = > {
          this.dep.notify()
        })
      }
    } else if (this.sync) {
      this.run()
    } else {
      queueWatcher(this)}}}Copy the code

Is computed and sync them according to different state analysis, ignore, then it will come to queueWatcher (this) function, it defined in SRC/core/observer/scheduler. In js:

const queue: Array<Watcher> = []
let has: { [key: number]: ?true } = {}
let waiting = false
let flushing = false
/** * Push a watcher into the watcher queue. * Jobs with duplicate IDs will be skipped unless it's * pushed when the queue is being flushed. */
export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if(! flushing) { queue.push(watcher) }else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1.0, watcher)
    }
    // queue the flush
    if(! waiting) { waiting =true
      nextTick(flushSchedulerQueue)
    }
  }
}
Copy the code

QueueWatcher is actually a concept of a queue that pushes all watcher updates to the queue,

The has object is designed to ensure that the same watcher is added only once. QueueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher = queueWatcher To ensure that there are no duplicate Watcher in a queue.

Finally, through waiting state, the call to nextTick(flushSchedulerQueue) can be guaranteed only once. NextTick is an asynchronous implementation, which is skipped in this article, but can be interpreted as logic in the nextTick (JS is single threaded). So once we get here, the queue is done, so the flushSchedulerQueue is just going to go through the queue, do something, and let’s see what the flushSchedulerQueue does, Its definition in the SRC/core/observer/scheduler in js:

let flushing = false
let index = 0
/** * Flush both queues and run the watchers. */
function flushSchedulerQueue () {
  flushing = true
  let watcher, id

  // Sort queue before flush.
  // This ensures that:
  // 1. Components are updated from parent to child. (because parent is always
  // created before the child)
  // 2. A component's user watchers are run before its render watcher (because
  // user watchers are created before the render watcher)
  // 3. If a component is destroyed during a parent component's watcher run,
  // its watchers can be skipped.
  queue.sort((a, b) = > a.id - b.id)

  // do not cache length because more watchers might be pushed
  // as we run existing watchers
  for (index = 0; index < queue.length; index++) {
    watcher = queue[index]
    if (watcher.before) {
      watcher.before()
    }
    id = watcher.id
    has[id] = null
    watcher.run()
    // in dev build, check and stop circular updates.
    if(process.env.NODE_ENV ! = ='production'&& has[id] ! =null) {
      circular[id] = (circular[id] || 0) + 1
      if (circular[id] > MAX_UPDATE_COUNT) {
        warn(
          'You may have an infinite update loop ' + (
            watcher.user
              ? `in watcher with expression "${watcher.expression}"`
              : `in a component render function.`
          ),
          watcher.vm
        )
        break}}}// keep copies of post queues before resetting state
  const activatedQueue = activatedChildren.slice()
  const updatedQueue = queue.slice()

  resetSchedulerState()

  // call component updated and activated hooks
  callActivatedHooks(activatedQueue)
  callUpdatedHooks(updatedQueue)

  // devtool hook
  /* istanbul ignore if */
  if (devtools && config.devtools) {
    devtools.emit('flush')}}Copy the code

A queue is sorted, and the comment says three scenarios:

  1. Component updates are parent to child, and because the component creation process is parent to child, parent is requiredwatcherIn the front, sonwatcherIn the back, father and son.
  2. We define a component object and write it in codewatcherProperty, or we call$watchMethod is createduser watcherIt’s renderingwatcherBefore, souser watcherPut it in the front as well.
  3. When a child component is destroyed, the parent component is destroyedwatcherWhen executed in a callback, a component is executed in the parent component’swatcherExecution period is destroyed, then it corresponds towatcherExecution can be skipped, so the parent component’swatcherIt should be executed first.

So those are the three points in the queue, from the largest to the smallest, and then you start traversing.

Run () = run(); run() = run(); run() = run(); run() = run(); Has [id] = null; has[id] = null; = null? Because of the watcher.run() function, which does a layer of callback, the logic in the callback may trigger queueWatcher again, so that the queue length changes:

export function queueWatcher (watcher: Watcher) {
  const id = watcher.id
  if (has[id] == null) {
    has[id] = true
    if(! flushing) { queue.push(watcher) }else {
      // if already flushing, splice the watcher based on its id
      // if already past its id, it will be run next immediately.
      let i = queue.length - 1
      while (i > index && queue[i].id > watcher.id) {
        i--
      }
      queue.splice(i + 1.0, watcher)
    }
    // ...}}Copy the code

And you can see that when flushing is true, it’s going to do else logic, and then it’s going to go from back to back, and it’s going to find the first place where the ID of the watcher that you want to insert is bigger than the id of the watcher in the current queue. Insert watcher into queue by ID, so the length of queue changes.

That’s why we do it, rightforWhen we loop, we don’t need a variable to hold itqueue.lengthInstead, the reason for calculating this length every time gives an infinite update error.

Then look at the watcher. The run () is stem what of, it is defined in SRC/core/observer/watcher. In js:

class Watcher {
  /** * Scheduler job interface. * Will be called by the scheduler. */
  run () {
    if (this.active) {
      this.getAndInvoke(this.cb)
    }
  }

  getAndInvoke (cb: Function) {
    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
      this.dirty = false
      if (this.user) {
        try {
          cb.call(this.vm, value, oldValue)
        } catch (e) {
          handleError(e, this.vm, `callback for watcher "The ${this.expression}"`)}}else {
        cb.call(this.vm, value, oldValue)
      }
    }
  }
}
Copy the code

The run function simply executes the getAndInvoke method and passes watcher’s callback function cb.

The getAndInvoke function is also simpler. First get a new value value and then compare the new value with the previous value. If the value is not equal, or if the new value is an object, or if it is a deep watcher, The cb callback will pass the first and second arguments to the new value and the oldValue. This is why we can get new and old values when we customize watcher.

To render Watcher, its cb callback is an empty function that calls get() when value = this.get() :

get () {
  pushTarget(this)
  let value
  const vm = this.vm
  try {
    value = this.getter.call(vm, vm)
  } catch (e) {
    if (this.user) {
      handleError(e, vm, `getter for watcher "The ${this.expression}"`)}else {
      throw e
    }
  } finally {
    // "touch" every property so they are all tracked as
    // dependencies for deep watching
    if (this.deep) {
      traverse(value)
    }
    popTarget()
    this.cleanupDeps()
  }
  return value
}
Copy the code

It executes the getter, so for rendering Watcher:

new Watcher(vm, updateComponent, noop, {
  before () {
    if (vm._isMounted) {
      callHook(vm, 'beforeUpdate')}}},true /* isRenderWatcher */)
Copy the code

The cb callback is a noop(empty function defined in SRC /shared/util.js) and its getter is an updateComponent:

updateComponent = () = > {
  vm._update(vm._render(), hydrating)
}
Copy the code

When getAndInvoke is executed, updateComponent is executed again, and vm._update and vm._render are called again for re-rendering.

NextTick: flushSchedulerQueue; watcher.run(); getAndInvoke; In the case of the render Watcher, the render is redone.

Finally, after a series of operations in flushSchedulerQueue, a resetSchedulerState function is called:

const queue: Array<Watcher> = []
let has: { [key: number]: ?true } = {}
let circular: { [key: number]: number } = {}
let waiting = false
let flushing = false
let index = 0
/** * Reset the scheduler's state. */
function resetSchedulerState () {
  index = queue.length = activatedChildren.length = 0
  has = {}
  if(process.env.NODE_ENV ! = ='production') {
    circular = {}
  }
  waiting = flushing = false
}
Copy the code

Restore the variables that control the state of the flow to their initial values and empty the Watcher queue.

conclusion

The dependency collection is to queue the watcher subscribed to the data change and issue the update. In essence, when the response data change occurs, all the watcher subscribed to the data change are informed to perform update and flush after nextTick. Put all changes into the next tick to avoid asynchronous execution multiple times.

My public number: the front end stack in the road, a front end article every day, the feeling of chewing is wonderful ~