Home >Web Front-end >JS Tutorial >12 Vue high-frequency principle interview questions (with analysis)

12 Vue high-frequency principle interview questions (with analysis)

青灯夜游
青灯夜游forward
2020-07-13 16:17:2752080browse

12 Vue high-frequency principle interview questions (with analysis)

This article shares 12 high-frequency Vue principle interview questions, covering the core implementation principles of Vue. In fact, it is impossible to explain the implementation principles of a framework in one article. I hope that through these 12 This question allows readers to have a certain understanding of their own Vue mastery (number B), so as to make up for their own shortcomings and better master Vue.

【Related recommendations: vue interview questions(2021)】

1. Vue responsiveness principle

12 Vue high-frequency principle interview questions (with analysis)

##Core implementation class:

Observer: its The function is to add getters and setters to the properties of the object for dependency collection and dispatch updates

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

Watcher: Observer object, the instance is divided into three types: rendering watcher (render watcher), calculated attribute watcher (computed watcher), listener watcher (user watcher)

Watcher and Dep Dep is instantiated in the relationship

watcher and adds subscribers to dep.subs. dep traverses dep.subs through notify to notify each watcher update.

Dependency collection

    InitState, when the computed attribute is initialized, the computed watcher is triggered Dependency collection
  1. InitState, when the listening attribute is initialized, the user watcher is triggered The process of dependency collection
  2. render() triggers the render watcher. When dependency collection
  3. re-renders, vm.render() is executed again, which will remove all watcher subscriptions in subs and re-render. Assignment.
Dispatch updates

    The response data has been modified in the component and the logic of triggering the setter
  1. Call dep.notify()
  2. Traverse all subs (Watcher instances) and call the update method of each watcher.
Principle

When creating a Vue instance, vue will traverse the properties of the data option, and use Object.defineProperty to add getters and setters to the properties to hijack the reading of data (getters are used (to collect dependencies, setters are used to dispatch updates), and track dependencies internally, notifying changes when properties are accessed and modified.

Each component instance will have a corresponding watcher instance, which will record all the data attributes of dependencies during the component rendering process (dependency collection, as well as computed watcher and user watcher instances), and then the dependencies are changed When the watcher instance is dependent on this data, the setter method will notify the watcher instance to recalculate (dispatch updates), thereby re-rendering its associated component.

Summary in one sentence:

vue.js uses data hijacking combined with publish-subscribe mode, hijacks the setters and getters of each property through Object.defineproperty, and publishes messages to subscribers when the data changes. , triggering the response listening callback

2. The implementation principle of computed

computed is essentially a lazy evaluation observer.

Computed internally implements a lazy watcher, that is, computed watcher. Computed watcher will not evaluate immediately and also holds a dep instance.

It internally uses the this.dirty attribute to mark whether the calculated property needs to be re-evaluated.

When the dependency status of computed changes, this lazy watcher will be notified.

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

If yes, it will be recalculated, and then the new and old values ​​will be compared. If it changes, it will be re-rendered. (

Vue wants to ensure that not only the value that the calculated property depends on changes, but when the final calculated value of the calculated property changes, the rendering watcher will be triggered to re-render, which is essentially an optimization. )

If not, just set this.dirty = true. (

When a calculated attribute depends on other data, the attribute will not be recalculated immediately. It will only be actually calculated when other places need to read the attribute later, that is, it has lazy (lazy calculation) characteristics. )

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

Difference

computed computed properties: depend on others Attribute value, and the computed value is cached. Only when the attribute value it depends on changes, the computed value will be recalculated the next time the computed value is obtained.

watch listener: more of an "observation" function,

No caching, similar to the monitoring callback of certain data, the callback will be executed whenever the monitored data changes Follow up.

Application scenario

Application scenario:

When we need to perform numerical calculations and depend on other data, computed should be used, because the cache feature of computed can be used to avoid Each time a value is obtained, it is recalculated.

When we need to perform asynchronous or expensive operations when the data changes, we should use watch. Using the watch option allows us to perform an asynchronous operation (access an API), limit the frequency with which we perform the operation, and when we get Before the final result, set the intermediate state. These are things that computed properties cannot do.

4. Why is Proxy adopted in Vue3.0 and Object.defineProperty abandoned?

Object.defineProperty itself has a certain ability to monitor changes in array subscripts, but in Vue, considering the cost-effectiveness of performance/experience, Youda abandoned this feature ( Why can’t Vue detect array changes). In order to solve this problem, after Vue internal processing, you can use the following methods to monitor the array
push();
pop();
shift();
unshift();
splice();
sort();
reverse();

Since only the above 7 methods are hacked, the properties of other arrays cannot be detected. It still has certain limitations.

Object.defineProperty can only hijack the properties of an object, so we need to traverse each property of each object. In Vue 2.x, data monitoring is achieved by recursively traversing the data object. If the attribute value is also an object, then deep traversal is required. Obviously, it is a better choice if a complete object can be hijacked.

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

#5. What is the use of keys in Vue?

key is the unique id given to each vnode. Depending on the key, our diff operation can be more accurate and faster (the diff node is also faster for simple list page rendering, but There will be some hidden side effects, such as the transition effect may not be produced, or some nodes have bound data (form) states, and state misalignment will occur.)

Diff algorithm will first be performed Cross-compare the head and tail of the old and new nodes. When there is no match, the new node's key will be used to compare with the old node to find the corresponding old node.

is more accurate: because with the key, it is not reused in place. , in-place reuse can be avoided in the sameNode function a.key === b.key comparison. Therefore, it will be more accurate. If the key is not added, the status of the previous node will be retained, which will produce a series of bugs.

Faster: The uniqueness of key can be fully utilized by the Map data structure. Compared with the time complexity of traversal search O(n), the time complexity of 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;
}

6. Let’s talk about the principle of nextTick

JS operating mechanism

JS execution is single-threaded and it is based on the event loop. The event loop is roughly divided into the following steps:

  1. All synchronization tasks are executed on the main thread, forming an execution context stack.
  2. Besides the main thread, there is also a "task queue". As long as the asynchronous task has running results, an event is placed in the "task queue".
  3. Once all synchronization tasks in the "execution stack" have been 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 continues to repeat the third step above.

12 Vue high-frequency principle interview questions (with analysis)

The execution process of the main thread is one tick, and all asynchronous results are scheduled through the "task queue". The message queue stores tasks one by one. The specification stipulates that tasks are divided into two categories, namely macro tasks and micro tasks, and after each macro task ends, 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);
  }
}

In the browser environment:

Common macro tasks include setTimeout, MessageChannel, postMessage, setImmediate

Common micro tasks includeMutationObsever and Promise.then

Asynchronous update queue

Maybe you haven’t noticed yet, Vue is# when updating the DOM ##AsynchronousExecuted. As long as it listens for data changes, 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 deduplication during buffering is important to avoid unnecessary calculations and DOM operations.

Then, on the next event loop "tick", Vue flushes the queue and performs the actual (deduplicated) work.

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

In the source code of vue2.5, the macrotask downgrade scheme is: setImmediate, MessageChannel, setTimeout

vue 的 nextTick 方法的实现原理:

  1. vue 用异步队列的方式来控制 DOM 更新和 nextTick 回调先后执行
  2. microtask 因为其高优先级特性,能确保队列中的微任务在一次事件循环前被执行完毕
  3. 考虑兼容问题,vue 做了 microtask 向 macrotask 的降级方案

7. vue 是如何对数组方法进行变异的 ?

我们先来看看源码

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]);
  }
};

简单来说,Vue 通过原型拦截的方式重写了数组的 7 个方法,首先获取到这个数组的ob,也就是它的 Observer 对象,如果有新的值,就调用 observeArray 对新的值进行监听,然后手动调用 notify,通知 render watcher,执行 update

8. Vue 组件 data 为什么必须是函数 ?

new Vue()实例中,data 可以直接是一个对象,为什么在 vue 组件中,data 必须是一个函数呢?

因为组件是可以复用的,JS 里对象是引用关系,如果组件 data 是一个对象,那么子组件中的 data 属性值会互相污染,产生副作用。

所以一个组件的 data 选项必须是一个函数,因此每个实例可以维护一份被返回对象的独立的拷贝。new Vue 的实例是不会被复用的,因此不存在以上问题。

9. 谈谈 Vue 事件机制,手写$on,$off,$emit,$once

Vue 事件机制 本质上就是 一个 发布-订阅 模式的实现。
class Vue {
  constructor() {
    //  事件通道调度中心
    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;
  }
}

10. 说说 Vue 的渲染过程

12 Vue high-frequency principle interview questions (with analysis)

  1. 调用 compile 函数,生成 render 函数字符串 ,编译过程如下:
  • parse 函数解析 template,生成 ast(抽象语法树)
  • optimize 函数优化静态节点 (标记不需要每次都更新的内容,diff 算法会直接跳过静态节点,从而减少比较的过程,优化了 patch 的性能)
  • generate 函数生成 render 函数字符串
  1. 调用 new Watcher 函数,监听数据的变化,当数据发生变化时,Render 函数执行生成 vnode 对象
  2. 调用 patch 方法,对比新旧 vnode 对象,通过 DOM diff 算法,添加、修改、删除真正的 DOM 元素

11. 聊聊 keep-alive 的实现原理和缓存策略

export default {
  name: "keep-alive",
  abstract: true, // 抽象组件属性 ,它在组件实例建立父子关系的时候会被忽略,发生在 initLifecycle 的过程中
  props: {
    include: patternTypes, // 被缓存组件
    exclude: patternTypes, // 不被缓存组件
    max: [String, Number] // 指定缓存大小
  },

  created() {
    this.cache = Object.create(null); // 缓存
    this.keys = []; // 缓存的VNode的键
  },

  destroyed() {
    for (const key in this.cache) {
      // 删除所有缓存
      pruneCacheEntry(this.cache, key, this.keys);
    }
  },

  mounted() {
    // 监听缓存/不缓存组件
    this.$watch("include", val => {
      pruneCache(this, name => matches(val, name));
    });
    this.$watch("exclude", val => {
      pruneCache(this, name => !matches(val, name));
    });
  },

  render() {
    // 获取第一个子元素的 vnode
    const slot = this.$slots.default;
    const vnode: VNode = getFirstComponentChild(slot);
    const componentOptions: ?VNodeComponentOptions =
      vnode && vnode.componentOptions;
    if (componentOptions) {
      // name不在inlcude中或者在exlude中 直接返回vnode
      // 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;
      // 获取键,优先获取组件的name字段,否则是组件的tag
      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;
      // 命中缓存,直接从缓存拿vnode 的组件实例,并且重新调整了 key 的顺序放在了最后一个
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance;
        // make current key freshest
        remove(keys, key);
        keys.push(key);
      }
      // 不命中缓存,把 vnode 设置进缓存
      else {
        cache[key] = vnode;
        keys.push(key);
        // prune oldest entry
        // 如果配置了 max 并且缓存的长度超过了 this.max,还要从缓存中删除第一个
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode);
        }
      }
      // keepAlive标记位
      vnode.data.keepAlive = true;
    }
    return vnode || (slot && slot[0]);
  }
};

原理

  1. 获取 keep-alive 包裹着的第一个子组件对象及其组件名
  2. 根据设定的 include/exclude(如果有)进行条件匹配,决定是否缓存。不匹配,直接返回组件实例
  3. 根据组件 ID 和 tag 生成缓存 Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值并更新该 key 在 this.keys 中的位置(更新 key 的位置是实现 LRU 置换策略的关键)
  4. 在 this.cache 对象中存储该组件实例并保存 key 值,之后检查缓存的实例数量是否超过 max 的设置值,超过则根据 LRU 置换策略删除最近最久未使用的实例(即是下标为 0 的那个 key)
  5. 最后组件实例的 keepAlive 属性设置为 true,这个在渲染和执行被包裹组件的钩子函数会用到,这里不细说

LRU 缓存淘汰算法

LRU(Least recently used)算法根据数据的历史访问记录来进行淘汰数据,其核心思想是“如果数据最近被访问过,那么将来被访问的几率也更高”。

12 Vue high-frequency principle interview questions (with analysis)

keep-alive 的实现正是用到了 LRU 策略,将最近访问的组件 push 到 this.keys 最后面,this.keys[0]也就是最久没被访问的组件,当缓存实例超过 max 设置值,删除 this.keys[0]

12. vm.$set()实现原理是什么?

受现代 JavaScript 的限制 (而且 Object.observe 也已经被废弃),Vue 无法检测到对象属性的添加或删除。

由于 Vue 会在初始化实例时对属性执行 getter/setter 转化,所以属性必须在 data 对象上存在才能让 Vue 将它转换为响应式的。

对于已经创建的实例,Vue 不允许动态添加根级别的响应式属性。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式属性。

那么 Vue 内部是如何解决对象新增属性不能响应的问题的呢?

export function set(target: Array<any> | Object, key: any, val: any): any {
  // target 为数组
  if (Array.isArray(target) && isValidArrayIndex(key)) {
    // 修改数组的长度, 避免索引>数组长度导致splice()执行有误
    target.length = Math.max(target.length, key);
    // 利用数组的splice变异方法触发响应式
    target.splice(key, 1, val);
    return val;
  }
  // target为对象, key在target或者target.prototype上 且必须不能在 Object.prototype 上,直接赋值
  if (key in target && !(key in Object.prototype)) {
    target[key] = val;
    return val;
  }
  // 以上都不成立, 即开始给target创建一个全新的属性
  // 获取Observer实例
  const ob = (target: any).__ob__;
  // target 本身就不是响应式数据, 直接赋值
  if (!ob) {
    target[key] = val;
    return val;
  }
  // 进行响应式处理
  defineReactive(ob.value, key, val);
  ob.dep.notify();
  return val;
}
  1. 如果目标是数组,使用 vue 实现的变异方法 splice 实现响应式
  2. 如果目标是对象,判断属性存在,即为响应式,直接赋值
  3. 如果 target 本身就不是响应式,直接赋值
  4. 如果属性不是响应式,则调用 defineReactive 方法进行响应式处理

本文转载自:https://segmentfault.com/a/1190000021407782

推荐教程:《JavaScript视频教程

The above is the detailed content of 12 Vue high-frequency principle interview questions (with analysis). For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:segmentfault.com. If there is any infringement, please contact admin@php.cn delete