Summarize Vue2 part of the common API principle, convenient development and review when standby

The main contents are as follows:

  • A brief introduction to the implementation principle of Vue2: Briefly introduce the functions of Observer, Dep and Watcher
  • Two, Vue2 response principle
  • Three, Vue2 bidirectional binding principle
  • Iv. Implementation principles of computed
  • Five, nextTick implementation principle

A brief introduction to the implementation principle of Vue2: Briefly introduce the functions of Observer, Dep and Watcher

Functions of the Observer:

Vue2 uses object.defineProperty (obj, key, handle) to convert the properties in data in our code to getter and setter responsively so that the data in data can be obtained. Data changes trigger registered GET and set events, which trigger view updates and other operations. This Object. Defineproperty () process is implemented by an Observer

The role of the Observer in Vue2

function defineReactive(obj, key, val) {
  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: () = > {
      console.log('Triggered when data is fetched');
      return val;
    },
    set: newVal= > {
      if (val === newVal) {
        return;
      }
      val = newVal;
      console.log("Triggered when data changes."); }})}let data = {
  test: 'Initial value'};// Bind the test attribute on the data
defineReactive(data, 'test', data.test);

console.log(data.test); // Triggered when data is fetched
data.test = 'hello Vue'; // Triggered when data changes
Copy the code

The role of Dep:

There are many properties in the data, but we may only use some of them. Dep is used to collect the dependencies that correspond to the properties in the data, and then when the set is triggered, notify the dependencies that are being collected, perform view updates, and so on, in a publy-subscribe mode

Note: A dependency is a Watcher

Dep is like a function center for intermediate scheduling. Dep helps us collect (exactly where to send notifications). In the following case, we know that there are test and MSG properties in the data, but only MSG is rendered to the page. As for test, no matter how changes are made, the display of the view will not be affected. Therefore, we can only collect the update operations that are affected by the changes in MSG

Now, the Dep of the corresponding property MSG collects a dependency that is used to manage MSG changes in the data

The role of Dep in Vue2

<div>
  <p>{{msg}}</p>
</div>

data: {
  test: 'Initial value'.msg: 'hello vue',}Copy the code

When we use watch to listen for changes in the MSG property, we notify the watch hook when the MSG changes and ask it to perform a callback function. At this point, the Dep corresponding to the MSG collects two dependencies. The second dependency is used to manage the MSG changes in the watch

watch: {
  msg: function (newVal, oldVal) {
    console.log('newVal:',newVal, 'oldVal:',oldVal)
  },
}
Copy the code

User-defined computed computing properties, such as the newMsg attribute, are dependent on MSG changes. So we also have to notify computed when the MSG changes and have it perform the callback function. At this point, the MSG Dep collects the third dependency that is used to manage MSG changes in the computed

Note: an attribute may have multiple dependencies, and each responsive data has a Dep to manage its dependencies

computed: {
  newMsg() {
    return this.msg + 'new'; }}Copy the code

We use object.defineProperty () in the Observer to collect dependencies by triggering the GET method when a property is read. The code is as follows:

function defineReactive (obj, key, val) {
  let Dep; // Dependent objects

  Object.defineProperty(obj, key, {
    enumerable: true.configurable: true.get: () = > {
      console.log('Data retrieved');
      // Collect the current dependency
      Dep.depend(); // This collection is dependent
      return val;
    },
    set: newVal= > {
      if (val === newVal) {
        return;
      }
      val = newVal;
      // The data changes, notifying the collection dependency to perform the update operation
      Dep.notify(); // Send a message to the subscriber to perform the update operation
      console.log("Data changed, perform update operation."); }})}Copy the code

Watcher’s role:

When the MSG changes, the three Watcher instances will be notified, and the three Watcher will perform their own operations. The Watcher will be able to control itself to belong to the data property. Or watch data listener or computed, so there are two methods in Watcher, one to notify changes and perform updates, and the other to add its own instance to the Dep’s dependency collection

class Watcher {
  addDep() {
    // Add the Watcher instance to the Dep dependency collection
  },
  update() {
    // When data changes, update operations such as rendering are performed accordingly}},Copy the code

Two, Vue2 response principle

  • The simple idea is that VUE does three things: data hijacking, dependency tracking, and dispatch updates
  • 1. Data hijacking: When the component instance is initialized, register getters and setters for each Data property via object.defineProperty ()
  • 2. The second step is dependency tracing: When a Watcher instance is created when the component instance is mounted, the component mount will execute render function, and then obtain the properties in data through set, and collect and trace the dependent properties
  • 3. Step 3 Dispatch updates: When the data changes, the corresponding set will be triggered, and the subscribed properties will be notified through the Watcher instance to update the view

Three, Vue2 bidirectional binding principle

  • Vue2 two-way binding adopts the mode of data hijacking and publisher – subscriber mode, hijack the setters and getters of each property through object.defineProperty (), publish messages to subscribers when data changes, and trigger the corresponding listening callback
  • First, you need to hijack the data listening, implementing a listener Observer that listens for all properties and notifying subscribers if there are changes
  • 2, implement a subscriber Watcher, according to the subscription property change notification, perform the corresponding update function, so as to update the view
  • 3. You also need a parser Compiler, which records and parses the corresponding node instructions to initialize the corresponding subscriber based on the initialization template data

Iv. Implementation principles of computed

Computed is an indolently evaluated observer, and comupted internally implements an indolently evaluated computed Watcher, which also does not evaluate immediately, and also has a Dep instance, The internal implementation uses the this.dirty attribute flag to evaluate whether the attribute is reevaluated, and then notifying computed Watcher when the state of the computed dependency changes, Computed Watcher uses this.dep.subs.length (which is the dependent subscribers collected by the intermediate schedule above) to determine if there are any subscribers. If there are subscribers, computed is re-evaluated and compared to the old value to see if the new value is the same, and if not, Notifying the subscriber of watcher to rerender, etc

Note: The purpose of Vue is to ensure that the value of the final computed value changes and trigger watcher to rerender, not only the dependent properties change, so that the performance can be improved. For this reason, computed is computed only when the current computed properties need to be obtained elsewhere

Five, nextTick implementation principle

Note: Understand javascript event loops before you understand nextTick

Usually we come across this demand: When a certain state changes, Dom is rerendered and the height of the element of the corresponding component is obtained. If we directly obtain the height of the element after the attribute changes, the value obtained is incorrect, in this case, we need to use nextTick and perform Dom operations in the callback function of nextTick

Why is it wrong to get a value directly from a state change?

<template <div class="test"> <el-button type="primary" @click="showContent"> isShow ? }}</el-button> <div class="content" ref="content" v-if="isShow"> </div> </div> </template> <script> export default { name: "Test", data() { return { isShow: false } }, methods: { showContent() { this.isShow = ! $refs.content console.log(' sync ',content) // Sync (' sync ',content) <div class="content"> </div> console.log(' height: ', content1.clientHeight) // }) } } }; </script> <style> .content { width: 100%; height: 60px; line-height: 60px; background: gray; } </style>Copy the code

Vue official description: In case you haven’t noticed, Vue performs DOM updates asynchronously. As soon as data changes are listened for, Vue opens a queue and buffers all data changes that occur in the same event loop. If the same watcher is fired multiple times, it will only be pushed into the queue once. This removal of duplicate data at buffering time is important to avoid unnecessary computation and DOM manipulation. Then, in the next event loop “tick”, Vue refreshes the queue and performs the actual (deduplicated) work. Vue internally tries to use native Promise. Then, MutationObserver, and setImmediate for asynchronous queues, and if the execution environment does not support it, setTimeout(fn, 0) is used instead

If we update the data frequently, Vue will not update the view immediately. By putting the update operation in the queue and simultaneously deprocessing the data, we can improve the performance. In the end, We need to tell the Vue layer through the nextTick callback function to help us execute the nextTick callback function when the view is updated to complete our requirements

After setting this.isshow =! When this. IsShow is displayed, Vue does not update the DOM data immediately, but instead puts the operation in a queue; If we do it repeatedly, the queue will be deduplicated; After all data changes in the same event loop are completed, the events in the queue are processed to improve overall performance

NextTick function implementation, pending control timerFunc can only be executed once at a time

  const callbacks = []
  let pending = false
  let timerFunc

  export function nextTick(cb? :Function, ctx? :Object) {
    let _resolve
    callbacks.push(() = > {
      if (cb) { // Perform the callback
        try {
          cb.call(ctx)
        } catch (e) {
          handlerError(e, ctx, 'nextTick')}}else if (_resolve) {
        _resolve(ctx)
      }
    })

    if(! pending) { pending =true TimerFunc can be executed only once at a time
      timerFunc() // A function that is compatible with asynchronous functions to execute callbacks queues
    }

    if(! cb &&typeof Promise! = ='undefined') {
      return new Promise(resolve= > {
        _resolve = resolve
      })
    }
  }
Copy the code

The purpose of the timerFunc function is to degrade the current environment repeatedly, trying to use native Promise. Then, MutationObserver, and setImmediate, none of which supports setTimeout. The purpose of the reversion is to put flushCallbacks into either the microtask (Judgment 1 and 2) or the macro task (judgment 3 and 4) and wait for the next event loop to execute

export let isUsingMicroTask = false
  if (typeof Promise! = ='undefined' && isNative(Promise)) {
    // Determine whether promises are supported native
    const p = Promise.resolve()
    timerFunc = () = > {
      // flushCallbacks is used to execute callbacks in callbacks
      p.then(flushCallbacks)
      if (isIOS) setTimeout(noop)
    }
    isUsingMicroTask = true
  } else if(! isIE &&typeofMutationObserver ! = ='undefined' && isNative(MutationObserver) || MutationObserver.toString() === '[object MutationObserverConstructor]') {
    // Determine whether MutationObserver is supported native
    let counter = 1
    const observer = new MutationObserver(flushCallbacks)
    const textNode = document.createTextNode(String(counter))
    observer.observe(textNode, {
      characterData: true
    })
    timerFunc = () = > {
      counter = (counter + 1) % 2
      textNode.data = String(counter)
    }
    isUsingMicroTask = true
  } else if (typeofsetImmediate ! = ='undefined' && isNative(setImmediate)) {
    // Determine whether the native state supports setImmediate
    timerFunc = () = > {
      setImmediate(flushCallbacks)
    }
  } else {
    // setTimeout is compatible with setTimeout
    timerFunc = () = > {
      setTimeout(flushCallbacks, 0)}}Copy the code

The timerFunc function is used to execute callbacks

  function flushCallbacks() {
    pending = false
    const copies = callbacks.slice(0)
    callbacks.length = 0
    for (let i = 0; i < copies.length; i++) {
      copies[i]()
    }
  }
Copy the code