This article is a part of the vue3.0-proxy change monitor. The purpose of this article is to explain and solve the problems left over from the previous two articles

Issue review

  1. What would the dependency collection process look like if you nested another subscriber in the getter function of one subscriber
  2. How to solve the problem of the subscriber updating multiple times in a round of changes where multiple attributes of the subscriber’s dependency are changed or one attribute of the dependency is changed multiple times

Dependency collection in the case of nested subscribers

Take the 3.0 implementation as an example,(2.x is almost the same) when the subscriber method is executed, the global activeEffect will be set to the effect of the current subscriber, and the real subscriber can be found through the global activeEffect when the dependency collection or dependency update is triggered during the execution of the subscriber method But imagine what would happen if subscriber A’s method nested subscriber B’s effect:

  1. activeEffectSet to effectA
  2. Execute subscriber A method to collect subscriber A’s dependencies
  3. Execute effectB of subscriber B
  4. willactiveEffectSet to effectB
  5. Do the dependency collection for subscriber B
  6. EffectB will be executedactiveEffectSet to null
  7. Execute the residual logic of subscriber A’s method

. The problem is clear: step 7 sets activeEffect to null during the execution of subscriber A’s residual logic, and subsequent dependency collection will not find the subscriber. The solution is clear: just reset activeEffect to effectA after step 6effectB is complete:

// Extend a global activeEffect stack to store the subscriber list const effectStack = [] window.activeEffect = null Const effect = function reactiveEffect() {improved effect function to push current effect into effectStack list when assigning current effect to activeEffect before executing subscriber method Cleanup (effect) // cleanup all dependencies before each subscriber method run, Effectstack.push (effect) activeEffect = effect try {fn() // The subscriber method references the data, triggering the getter for the referenced data for dependency collection} finally {// Pop () activeEffect = effectStack[effectStack.length-1] // Set activeEffect to top of stack after dependency collection}}Copy the code

Subscriber updates policy after notification of change

Let’s start with the issue of publishing data response: If we continuously change the data, continuous change notifications will trigger frequent updates from subscribers, which may not be what we expect, but also can cause serious performance issues. The root cause is that change notifications and subscriber updates are synchronized. In many cases, a subscriber may depend on multiple data. If the implementation of the original version is followed, data A updates will trigger subscriber updates, and data B updates will trigger subscriber updates again. It would be easy to think of synchronous change notifications, but the subscriber updates would be done asynchronously, so that the subscriber updates would be performed uniformly after a round of change notifications The code implementation takes 3.0 as an example. 2.x is the same (the following code has not been tested, just to provide ideas):

Const queue = [] // let isFlushing = false, isFlushing = false CurrentFlushPromise = null const scheduler = function(job) { queue.includes(job)) { queue.push(job) queueFlush() } } function queueFlush() { if (! isFlushing && ! isFlushPending) { isFlushPending = true currentFlushPromise = Promise.resolve().then(flushJobs) } } function flushJobs()  { isFlushPending = false isflushing = true try { for (let i = 0; i < queue.length; I ++) {queue[I]()}} finally {isflushing = false currentFlushPromise = null queue. Length = 0} trigger() { const effects = new Set() const depsMap = targetMap.get(target) if (! depsMap) return function add(effectsToAdd) { effectsToAdd.forEach(effect => { if (effect ! == activeEffect) { effects.add(effect) } }) } if (! Depmap.get (key) return add(depmap.get (key)) function run(effect) {scheduler(effect) effects.forEach(run) }Copy the code

Vue3.0 uses promise.resolve () to add the subscriber update effect to the microtask queue as a job. In other words, change notification is synchronously notified as synchronous task, effect is added to queue through scheduler task scheduling, and the same task is filtered. When a round of change notification is delivered, JS engine will automatically check the microtask queue and synchronously execute the job queue added by scheduler Implement the expected extension that one subscriber depends on multiple data, and the subscriber updates only once in a round of data changes when multiple dependent data changes: There is a more complicated situation in VUE, If computed acts as both a subscriber and a dependent, when the data update notification page renders render-effect,computed recalculation will be invoked, and computed recalculation will notify its subscribers, so the queue will be in the process of task execution Since the change notification is synchronous, there are two cases:

  1. Computed subscribers already have a task queuequeue, it has no actual impact
  2. There is no task queue for computed subscribersqueue, is pushed to the queue for execution

The way to implement asynchrony in 2.x also includes using (new MessageChannel()).postmessage, but the general idea is similar to 3.x

Afterword.

This article focuses on the two questions left over from the first two articles. Of course, there is also a lot of branching logic in the specific implementation of VUE, as well as compatibility for many cases. See the source code for details, and the most commonly used reactive methods in development, data,computed, and Watch, will be introduced later.