A brief discussion on the principle of Vue response

This is the 24th day of my participation in the August More Text Challenge

preface

In this article, we mainly talk about Vue, from daily use to core principle implementation, step by step uncover the nature of Vue responsive principle.

The basic concept

First, in a fully responsive system, we need to recognize these concepts:

Observer: It adds getters and setters to properties of an object for dependency collection and distribution of updates

Dep: Collects the dependencies of the current responsive objects. Each responsive object, including its children, has a Dep instance (where subs is an array of Watcher instances). When data changes, each Watcher is notified via dep.notify().

Watcher: an observer object. The instance can be divided into three types: render Watcher (render Watcher), computed attribute Watcher (computed Watcher), and listener Watcher (user Watcher)

The key to the Watcher implementation is to know which variables depend on, and to make a call notification when those variables are updated. For details, refer to my previous article, which can be briefly mentioned here:

Depend on the collection

  1. InitState triggers a computed Watcher dependency collection when the computed property is initialized

  2. When initState, user Watcher dependency collection is triggered when the listener property is initialized

  3. The render() procedure triggers the Render Watcher dependency collection

  4. When re-render is executed,vm.render() removes the watcer subscriptions in all subs and reassigns them.

Distributed update

  1. The data in the response is modified in the component, triggering the logic of the setter

  2. Call dep. Notify ()

  3. Iterate through all subs (Watcher instances) and call the Update method of each Watcher.

computed

Computed in nature is an lazily evaluated observer.

Computed implements an inert WATcher internally, that is,computed Watcher, which does not evaluate immediately and holds an instance of DEP.

The this.dirty attribute tag internally calculates whether the attribute needs to be reevaluated.

When computed dependency status changes, the lazy Watcher is notified,

Computed Watcher detects subscribers by this.dep.subs.length,

Proxy

We all know that in Vue3, the previous Object.defineProperty was abandoned in favor of a Proxy. So why make this trade-off?

Object.defineproperty itself has some ability to detect array subscript changes, but in Vue, this feature is deprecated for performance/experience value reasons. To solve this problem, after internal vUE processing, you can use the following methods to listen on arrays

push(); pop(); shift(); unshift(); splice(); sort(); reverse();Copy the code

Because only the above 7 methods are hacked, other array attributes can not be detected, or there are certain limitations.

Object.defineproperty can only hijack attributes of objects, so we need to traverse each attribute of each Object. In Vue 2.x, the monitoring of data is achieved by recursion + traversal of the data object. If the attribute value is also an object, it needs deep traversal. Obviously, hijacking a complete object is a better choice.

A Proxy can hijack an entire object and return a new object. Proxy can Proxy not only objects, but also arrays. You can also proxy dynamically added properties.

nextTick

This is an API that we use a lot to do things after the view is updated. So how does nextTick work internally? First, we have to talk about how JS works

JS execution is single threaded and is based on event loops. The event cycle can be roughly divided into the following steps:

  1. All synchronization tasks are executed on the main thread, forming an execution Context stack.
  2. In addition to the main thread, there is a “task queue”. Whenever an asynchronous task has a result, an event is placed in the “task queue”.
  3. Once all synchronization tasks in the execution stack are completed, the system reads the task queue to see what events are in it. Those corresponding asynchronous tasks then end the wait state, enter the execution stack, and start executing.
  4. The main thread repeats step 3 above.

event-loop

The execution of the main thread is a tick, and all asynchronous results are scheduled through the “task queue”. Message queues hold individual tasks. According to the specification, tasks fall into two categories, macro task and Micro Task. After each Macro task is finished, all micro tasks should be cleared.

In the browser environment:

Common Macro tasks include setTimeout, MessageChannel, postMessage, and setImmediate

Common micro Tasks are MutationObsever and Promise.then

In the Vue2.5 source code, MacroTask degrades through setImmediate, MessageChannel, and setTimeout

Ok, let’s draw a conclusion. The realization principle of Vue’s nextTick method is as follows:

  1. Vue uses an asynchronous queue to control when DOM updates and nextTick callbacks are executed

  2. Due to its high-priority nature, MicroTask ensures that the microtasks in the queue are completed before an event loop

  3. Considering compatibility, Vue made a demotion scheme from microtask to Macrotask

summary

  • When a Vue instance is created, Vue iterates through the properties of the data option, hijacking the reading of the data by adding getter and setter to the property with Object.defineProperty (getters are used to rely on collection, setters) To distribute updates), and internally track dependencies, notifying properties of changes when they are accessed and modified.

  • Each component instance has a corresponding Watcher instance that records all the data properties of dependencies during component rendering (for dependency collection, for computed Watcher, for user Watcher instances), and setter methods notify dependencies of that data when they change The Watcher instance is recalculated (updates are distributed) to re-render its associated components.

  • Vue adopts data hijacking combined with publishe-subscribe mode. It hijacks the setter and getter of each attribute through Object.defineProperty, releases messages to subscribers when data changes, and triggers the response listening callback.