How many of the 12 vue high frequency principle interview questions can you answer?

Posted by ragtek on Tue, 04 Jan 2022 14:35:39 +0100

preface

This paper shares 12 interview questions on Vue high frequency principle, covering the core implementation principle of Vue. In fact, it is impossible to finish an article on the implementation principle of a framework. I hope that through these 12 questions, readers can have a certain understanding of their Vue Mastery (B number), so as to make up for their shortcomings and better master Vue ❤️

1. Vue responsive principle

vue-reactive

Core implementation class:

Observer: its function is to add getter s and setter s to the object's properties for dependency collection and distribution of updates

Dep: used to collect the dependencies of the current responsive object. Each responsive object, including child objects, has a dep instance (where subs is the watcher instance array). When the data changes, each Watcher will be notified through dep.notify().

Watcher: observer object. The instances are divided into render watcher, computed watcher and listener watcher

Relationship between Watcher and Dep

Dep is instantiated in the watcher and subscribers are added to dep.subs. Dep traverses dep.subs through notify to notify each watcher of updates.

Dependency collection

  1. initState: when initializing the computed attribute, the computed watcher dependency collection is triggered

  2. In initState, the user watcher dependency collection is triggered when the listening property is initialized

  3. The render() process triggers the render watcher dependency collection

  4. When re render, VM When render() is executed again, the subscription of watcer in all subs will be removed and re assigned.

Distribute updates

  1. The data of the response is modified in the component to trigger the logic of the setter

  2. Call dep.notify()

  3. Traverse all subs (watcher instances) and call the update method of each watcher.

principle

When creating a vue instance, vue will traverse the attributes of the data option, using object Defineproperty adds getters and setters for properties to hijack data reading (getters are used to collect dependencies and setters are used to distribute updates), tracks dependencies internally, and notifies changes when properties are accessed and modified.

Each component instance will have a corresponding watcher instance. During component rendering, all dependent data attributes (dependency collection, calculated watcher and user watcher instances) will be recorded. Later, when the dependency is changed, the setter method will notify the watcher instance that depends on this data to recalculate (send updates), so as to make its associated components re render.

One sentence summary:

vue.js adopts data hijacking combined with publish subscribe mode through object Define property to hijack setters and getters of various properties, publish messages to subscribers when data changes, and trigger the listening callback of response

2. Implementation principle of computed

computed is essentially an inert evaluator.

A lazy watcher is implemented inside computed, that is, computed watcher. Computed watcher does not evaluate immediately and holds a dep instance at the same time.

Its internal through this The dirty property marks whether the calculated property needs to be re evaluated.

When the dependency state of computed changes, the lazy watcher will be notified,

computed watcher through this dep.subs. Length determines whether there are subscribers,

If yes, it will be recalculated and then compared with the old and new values. If it changes, it will be rendered again. (Vue wants to ensure that it is not only the value that the calculation attribute depends on that changes, but also that the rendering watcher will be triggered to re render when the final calculated value of the calculation attribute changes. It is essentially an optimization.)

If not, just put this dirty = true. (when the calculated attribute depends on other data, the attribute will not be recalculated immediately. It will be calculated only when the attribute needs to be read elsewhere later, that is, it has the lazy feature.)

3. What are the differences and application scenarios between computed and watch?

difference

Computed calculated attribute: it depends on other attribute values, and the computed value is cached. Only when the dependent attribute value changes, the computed value will be recalculated the next time the computed value is obtained.

watch listener: it is more of an "observation" function and has no caching. It is similar to the monitoring callback of some data. Whenever the monitored data changes, the callback will be executed for subsequent operations.

Application scenario

Application scenario:

When we need to perform numerical calculation and rely on other data, we should use computed, because we can use the cache feature of computed to avoid recalculation every time we get a value.

When we need to perform asynchronous or expensive operations when data changes, we should use watch. The watch option allows us to perform asynchronous operations (access an API), limits the frequency of our operations, and sets the intermediate state before we get the final result. These are things that computational properties cannot do.

4. Why in vue3 0 uses Proxy and discards object defineProperty?

Object.defineProperty itself has a certain ability to monitor the changes of array subscripts, but in Vue, this feature is greatly abandoned in consideration of performance / experience cost performance( Why can't Vue detect array changes ). In order to solve this problem, after vue internal processing, you can use the following methods to listen to the array

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

Because the hack processing is only carried out for the above seven methods, the properties of other arrays can not be detected, which still has some limitations.

Object.defineProperty can only hijack the properties of objects, so we need to traverse each property of each object. Vue 2. In X, the data is monitored through recursion + traversal of data objects. If the attribute value is also an object, it needs deep traversal. Obviously, it is a better choice to hijack a complete object.

Proxy can hijack the whole object and return a new object. Proxy can not only proxy objects, but also proxy arrays. You can also dynamically add attributes to agents.

5. What is the use of the key in Vue?

Key is the unique id given to each vnode. Depending on the key, our diff operation can be more accurate and faster (for simple list page rendering, diff nodes are also faster, but there will be some hidden side effects, such as no transition effect, or there will be state dislocation when there is bound data (form) state on some nodes.)

In the process of diff algorithm, the beginning and end of the old and new nodes will be cross compared first. When they cannot be matched, the key of the new node will be compared with the old node to find the corresponding old node

More accurate: because the key is not used locally, the local reuse can be avoided in the comparison of a.key === b.key in the sameNode function. Therefore, it will be more accurate. If you do not add a key, the state of the previous node will be retained and a series of bug s will be generated.

Faster: the uniqueness of the key can be fully utilized by the Map data structure. Compared with the time complexity O (n), the time complexity of the Map is only O(1). The source code is as follows:

function createKeyToOldIdx(children, beginIdx, endIdx) {
  let i, key;
  const map = {};
  for (i = beginIdx; i <= endIdx; ++i) {
    key = children[i].key;
    if (isDef(key)) map[key] = i;
  }
  return map;
}
Copy code

6. Talk about the principle of nextTick

JS operation mechanism

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

  1. All synchronization tasks are executed on the main thread to form an execution context stack.
  2. In addition to the main thread, there is also a "task queue". As long as the asynchronous task has run results, an event will be placed in the "task queue".
  3. Once all synchronization tasks in the "execution stack" are executed, the system will read the "task queue" to see what events are in it. Those corresponding asynchronous tasks end the waiting state, enter the execution stack and start execution.
  4. The main thread repeats the third step above.

event-loop

The execution process of the main thread is a tick, and all asynchronous results are scheduled through the "task queue". Tasks are stored in the message queue. According to the specification, tasks are divided into two categories: macro tasks and micro tasks. After each macro task is completed, all micro tasks must be cleared.

for (macroTask of macroTaskQueue) {
  // 1. Handle current MACRO-TASK
  handleMacroTask();

  // 2. Handle all MICRO-TASK
  for (microTask of microTaskQueue) {
    handleMicroTask(microTask);
  }
}
Copy code

In the browser environment:

Common macro task s include setTimeout, MessageChannel, postMessage, and setImmediate

Common micro task s include mutationobserver and promise then

Asynchronous update queue

You may not have noticed that Vue executes asynchronously when updating the DOM. Whenever a data change is heard, Vue will open a queue and buffer all data changes that occur in the same event loop.

If the same watcher is triggered multiple times, it will only be pushed into the queue once. This de duplication in buffering is important to avoid unnecessary calculations and DOM operations.

Then, in the next event loop "tick", Vue refreshes the queue and performs the actual (de duplicated) work.

Vue internally attempts to use native promise. Com for asynchronous queues Then, MutationObserver and setImmediate. If the execution environment does not support it, setTimeout(fn, 0) will be used instead.

In vue2 In the source code of 5, the degradation schemes of macrotask are: setImmediate, MessageChannel and setTimeout

Implementation principle of vue's nextTick method:

  1. vue uses asynchronous queue to control DOM update and nextTick callback execution successively

  2. Because of its high priority, microtask can ensure that the micro tasks in the queue are executed before an event cycle

  3. Considering the compatibility problem, vue made a degradation scheme from microtask to macrotask

7. How does Vue mutate array methods?

Let's look at the source code first

const arrayProto = Array.prototype;
export const arrayMethods = Object.create(arrayProto);
const methodsToPatch = [
  "push",
  "pop",
  "shift",
  "unshift",
  "splice",
  "sort",
  "reverse"
];

/**
 * Intercept mutating methods and emit events
 */
methodsToPatch.forEach(function(method) {
  // cache original method
  const original = arrayProto[method];
  def(arrayMethods, method, function mutator(...args) {
    const result = original.apply(this, args);
    const ob = this.__ob__;
    let inserted;
    switch (method) {
      case "push":
      case "unshift":
        inserted = args;
        break;
      case "splice":
        inserted = args.slice(2);
        break;
    }
    if (inserted) ob.observeArray(inserted);
    // notify change
    ob.dep.notify();
    return result;
  });
});

/**
 * Observe a list of Array items.
 */
Observer.prototype.observeArray = function observeArray(items) {
  for (var i = 0, l = items.length; i < l; i++) {
    observe(items[i]);
  }
};
Copy code

In short, Vue rewrites the seven methods of the array through prototype interception. First, it obtains the ob of the array, that is, its Observer object. If there is a new value, it calls observeArray to listen for the new value, and then manually calls notify, notifies render watcher and executes update

8. Why must Vue component data be a function?

In the new Vue() instance, data can be an object directly. Why must data be a function in the vue component?

Because components can be reused and objects in JS are reference relationships, if component data is an object, the data attribute values in sub components will pollute each other and produce side effects.

Therefore, the data option of a component must be a function, so each instance can maintain an independent copy of the returned object. The instance of new Vue will not be reused, so the above problems do not exist.

9. Talk about Vue event mechanism, handwritten $on,$off,$emit,$once

Vue event mechanism is essentially an implementation of publish subscribe mode.

class Vue {
  constructor() {
    //Event channel dispatching center
    this._events = Object.create(null);
  }
  $on(event, fn) {
    if (Array.isArray(event)) {
      event.map(item => {
        this.$on(item, fn);
      });
    } else {
      (this._events[event] || (this._events[event] = [])).push(fn);
    }
    return this;
  }
  $once(event, fn) {
    function on() {
      this.$off(event, on);
      fn.apply(this, arguments);
    }
    on.fn = fn;
    this.$on(event, on);
    return this;
  }
  $off(event, fn) {
    if (!arguments.length) {
      this._events = Object.create(null);
      return this;
    }
    if (Array.isArray(event)) {
      event.map(item => {
        this.$off(item, fn);
      });
      return this;
    }
    const cbs = this._events[event];
    if (!cbs) {
      return this;
    }
    if (!fn) {
      this._events[event] = null;
      return this;
    }
    let cb;
    let i = cbs.length;
    while (i--) {
      cb = cbs[i];
      if (cb === fn || cb.fn === fn) {
        cbs.splice(i, 1);
        break;
      }
    }
    return this;
  }
  $emit(event) {
    let cbs = this._events[event];
    if (cbs) {
      const args = [].slice.call(arguments, 1);
      cbs.map(item => {
        args ? item.apply(this, args) : item.call(this);
      });
    }
    return this;
  }
}
Copy code

10. Talk about Vue's rendering process

render

  1. Call the compile function to generate the render function string. The compilation process is as follows:
  • parse function parses template and generates ast (abstract syntax tree)

  • The optimize function optimizes the static nodes (marks the contents that do not need to be updated every time, and the diff algorithm directly skips the static nodes, thus reducing the comparison process and optimizing the performance of patch)

  • The generate function generates a render function string

  1. Call the new Watcher function to listen for data changes. When the data changes, the Render function generates a vnode object

  2. Call the patch method, compare the old and new vnode objects, and add, modify and delete Real DOM elements through the DOM diff algorithm

11. Talk about the implementation principle and cache strategy of keep alive

export default {
  name: "keep-alive",
  abstract: true, //Abstract component attribute, which will be ignored when establishing parent-child relationship between component instances, occurs in the process of initLifecycle
  props: {
    include: patternTypes, //Cached component
    exclude: patternTypes, //Components not cached
    max: [String, Number] //Specify cache size
  },

  created() {
    this.cache = Object.create(null); //Cache
    this.keys = []; //Cached VNode key
  },

  destroyed() {
    for (const key in this.cache) {
      //Delete all caches
      pruneCacheEntry(this.cache, key, this.keys);
    }
  },

  mounted() {
    //Listen for cached / uncached components
    this.$watch("include", val => {
      pruneCache(this, name => matches(val, name));
    });
    this.$watch("exclude", val => {
      pruneCache(this, name => !matches(val, name));
    });
  },

  render() {
    //Gets the vnode of the first child element
    const slot = this.$slots.default;
    const vnode: VNode = getFirstComponentChild(slot);
    const componentOptions: ?VNodeComponentOptions =
      vnode && vnode.componentOptions;
    if (componentOptions) {
      //name is not in inlcude or in exclude} and returns vnode directly
      // check pattern
      const name: ?string = getComponentName(componentOptions);
      const { include, exclude } = this;
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode;
      }

      const { cache, keys } = this;
      //Get the key, and get the name field of the component first, otherwise it is the tag of the component
      const key: ?string =
        vnode.key == null
          ? // same constructor may get registered as different local components
            // so cid alone is not enough (#3269)
            componentOptions.Ctor.cid +
            (componentOptions.tag ? `::${componentOptions.tag}` : "")
          : vnode.key;
      //Hit the cache, directly get the component instance of vnode , from the cache, and readjust the order of , key , to the last
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        remove(keys, key);
        keys.push(key);
      }
      //If the cache is not hit, set {vnode} into the cache
      else {
        cache[key] = vnode;
        keys.push(key);
        // prune oldest entry
        //If {max} is configured and the cache length exceeds} this max, also delete the first one from the cache
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }
      //keepAlive flag bit
      vnode.data.keepAlive = true;
    }
    return vnode || (slot && slot[0]);
  }
};
Copy code

principle

  1. Get the first sub component object wrapped by keep alive and its component name

  2. Match the conditions according to the set include/exclude (if any) to determine whether to cache. If not, return the component instance directly

  3. Generate the cache key according to the component ID and tag, and find out whether the component instance has been cached in the cache object. If it exists, directly take out the cache value and update the key in this Location in keys (updating the location of keys is the key to realizing LRU replacement strategy)

  4. In this Store the component instance in the cache object and save the key value. Then check whether the number of cached instances exceeds the setting value of max. if it exceeds the setting value, delete the most recently unused instance (that is, the key with subscript 0) according to the LRU replacement policy

  5. Finally, the keepAlive attribute of the component instance is set to true, which will be used in rendering and executing the hook function of the wrapped component, which will not be described in detail here

LRU cache elimination algorithm

LRU (Least recently used) algorithm eliminates data according to the historical access record of data. Its core idea is that "if the data has been accessed recently, the probability of being accessed in the future is higher".

LRU

The implementation of keep alive uses the LRU policy to push the recently accessed components to this Keys last, this Keys [0] is the component that has not been accessed for the longest time. When the cache instance exceeds the max setting, delete this keys[0]

12. vm.$ What is the implementation principle of set()?

Due to the limitations of modern JavaScript (and Object.observe has been abandoned), Vue cannot detect the addition or deletion of object properties.

Because Vue will perform getter/setter conversion on the attribute when initializing the instance, the attribute must exist on the data object for Vue to convert it into a responsive.

For instances that have been created, Vue does not allow dynamic addition of root level responsive properties. However, you can use Vue The set (object, propertyname, value) method adds responsive properties to nested objects.

So how does Vue solve the problem that new attributes of objects cannot respond?

export function set(target: Array<any> | Object, key: any, val: any): any {
  //target is an array
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    //Modify the length of the array to avoid incorrect execution of splice() due to index > array length
    target.length = Math.max(target.length, key);
    //Using the splice mutation method of array to trigger the response formula
    target.splice(key, 1, val);
    return val;
  }
  //Target is the object, and the key is in target or target On prototype and must not be on object On prototype , direct assignment
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val;
  }
  //If none of the above is true, start creating a new attribute for target
  //Get Observer instance
  const ob = (target: any).__ob__;
  //target itself is not responsive data, so it is assigned directly
  if (!ob) {
    target[key] = val;
    return val;
  }
  //Perform responsive processing
  defineReactive(ob.value, key, val);
  ob.dep.notify();
  return val;
}
Copy code
  1. If the target is an array, use the mutation method splice implemented by vue to implement the response

  2. If the target is an object, judge the existence of the attribute, that is, it is a response type and assign a value directly

  3. If the target itself is not responsive, assign a value directly

  4. If the attribute is not responsive, the defineReactive method is called for responsive processing

By the way, recommend my novel website

Insect fish novel network

Zhiqiu novel network

These two novel websites include the hottest online novels at present, and provide the latest chapters of high-quality novels for free. They are the necessary novel reading networks for the majority of online novel lovers. Free online reading without pop-up window, no middleman to earn price difference, welcome to collect Yo

Topics: Vue Interview