vlambda博客
学习文章列表

Vue面试题(5)虚拟dom和key

本文收集和整理了有关虚拟dom和key的面试题,希望对准备面试和深入学习的小伙伴有所帮助!

注:文中题目不是原创,但为了添加为话题标为原创。题目选于多篇文章,又重新整理、整合、排序,再此一并感谢。收集材料时有的题目忽略了标注来源,如原作者希望标出来源,请联系小编。

1.谈一下对虚拟DOM的理解?虚拟DOM主要做了什么?虚拟DOM本身是什么?

什么是虚拟DOM?

从本质上来说,Virtual Dom是一个JS对象,通过对象的方式来表示DOM结构。

将页面的状态抽象为JS对象的形式,配合不同的渲染工具,使跨平台渲染成为可能。

通过事务处理机制,将多次DOM修改的结果一次性的更新到页面上,从而有效的减少页面渲染的次数,减少修改DOM的重绘重排次数,提高渲染性能

虚拟dom是对DOM的抽象,这个对象是更加轻量级的对DOM的描述。它设计的最初目的,就是更好的跨平台,比如node.js就没有DOM,如果想实现SSR,那么一个方式就是借助虚拟dom,因为虚拟dom本身就是js对象。

在代码渲染到页面之前,vue或者react会把代码转换成一个对象(虚拟DOM)。以对象的形式来描述真实dom结构,最终渲染到页面。

在每次数据发生变化前,虚拟dom都会缓存一份,变化之时,现在的虚拟dom会与缓存的虚拟dom进行比较

在vue或者react内部封装了diff算法,通过这个算法来进行比较,渲染时修改改变的变化,原先没有发生改变的通过原先的数据进行渲染。

另外现代前端框架的一个基本要求就是无需手动操作DOM,一方面是因为手动操作DOM无法保证程序性能,多人协作的项目中如果review不严格,可能会有开发写出性能较低的代码,另一方面更重要的是省略手动DOM操作可以大大提升开发效率

为什么要用Virtual Dom?

1.保证性能下限,再不进行手动优化的情况下,提供能过得去的性能

看一下页面渲染的一个过程

解析HTML --- 生成DOM --- 生成cssDom -- Layout -- Paint -- Compiler

下面对比一下修改DOM时真实DOM操作和Virtual Dom的过程,来看一下它们重排重绘的性能消耗

  • 真实DOM:生成HTML字符串  +  重建所有的DOM元素

  • Virtual Dom:生成VNode  +  DOMDiff  +  必要的dom更新

    Virtual Dom的更新DOM的准备工作耗费更多的时间,也就是js层面,相对于更多的DOM操作它的消费是极其便宜的。尤大大曾说到:框架给你的保证时,你不需要手动优化的情况下,我依然可以给你提供过得去的性能

2.跨平台

Virtual Dom本质上JS对象,可以很方便的跨平台操作,比如服务端渲染、uniapp等

Virtual Dom真的比真实DOM性能好么?

  • 1.首次渲染大量DOM时,由于多了一层虚拟DOM的计算,比innerHTML插入慢

  • 2.正如它能保证性能下限,在真实DOM操作的时候进行针对性的优化时,还是更快的。

2、再说一下虚拟Dom以及key属性的作用

由于在浏览器中操作DOM是很昂贵的。频繁的操作DOM,会产生一定的性能问题。这就是虚拟Dom的产生原因。

Vue2的Virtual DOM借鉴了开源库snabbdom的实现。

Virtual DOM本质就是用一个原生的JS对象去描述一个DOM节点。是对真实DOM的一层抽象。(也就是源码中的VNode类,它定义在src/core/vdom/vnode.js中。)

VirtualDOM映射到真实DOM要经历VNode的create、diff、patch等阶段。

key的作用是尽可能的复用 DOM 元素。

新旧 children 中的节点只有顺序是不同的时候,最佳的操作应该是通过移动元素的位置来达到更新的目的。

需要在新旧 children 的节点中保存映射关系,以便能够在旧 children 的节点中找到可复用的节点。key也就是children中节点的唯一标识

3、Vue 中的 key 有什么作用?

Vue 中 key 的作用是:key 是为 Vue 中 vnode 的唯一标记,通过这个 key,我们的 diff 操作可以更准确、更快速。

更准确:因为带 key 就不是就地复用了,在 sameNode 函数 a.key === b.key 对比中可以避免就地复用的情况。所以会更加准确。

更快速:利用 key 的唯一性生成 map 对象来获取对应节点,比遍历方式更快

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

或者说:需要使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点。作用主要是为了高效的更新虚拟DOM。

4、Vue 中 key 的作用(源码理解)

看是不错的回答:如果不加 key,那么 vue 会选择复用节点(Vue 的就地更新策略),导致之前节点的状态被保留下来,会产生一系列的 bug

不加 key 也不一定就会复用,关于 diff 和 key 的使用,建议大家还是找一些质量好的文章真正深入的看一下原理。

文章如下:

https://mp.weixin.qq.com/s?__biz=MzI3NTM5NDgzOA==&mid=2247483676&idx=1&sn=56885c423ec7700c499356c86189ba3d&chksm=eb043965dc73b073745701d2e92bf32629cb1c096e781e8ff728b9adc36d4c7b5a04ebe6dcea&token=1208709646&lang=zh_CN&scene=21#wechat_redirect

5、diff算法的时间复杂度?(源码理解)

看似不错的回答:两个数的完全的 diff 算法是一个时间复杂度为 o(n3), Vue 进行了优化 O(n3)复杂度的问题转换成 O(n)复杂度的问题(只比较同级不考虑跨级问题)

在前端当中,你很少会跨级层级地移动 Dom 元素,所以 Virtual Dom 只会对同一个层级元素进行对比

但实际:听这个描述来说,React 没有对 O(n3) 的复杂度进行优化?事实上 React 和 Vue 都只会对 tag 相同的同级节点进行 diff,如果不同则直接销毁重建,都是 O(n) 的复杂度。

6、Vue2.x和Vue3.x渲染器的diff算法分别说一下

简单来说,diff算法有以下过程

先同级比较再比较子节点

先判断一方有子节点和一方没有子节点的情况。如果新的一方有子节点,旧的一方没有,相当于新的子节点替代了原来没有的节点;同理,如果新的一方没有子节点,旧的一方有,相当于要把老的节点删除。

再来比较都有子节点的情况,这里是diff的核心。首先会通过判断两个节点的key、tag、isComment、data同时定义或不定义以及当标签类型为input的时候type相不相同来确定两个节点是不是相同的节点,如果不是的话就将新节点替换旧节点。

如果是相同节点的话才会进入到patchVNode阶段。在这个阶段核心是采用双端比较的算法,同时从新旧节点的两端进行比较,在这个过程中,会用到模版编译时的静态标记配合key来跳过对比静态节点,如果不是的话再进行其它的比较。


Vue3.x借鉴了ivi算法和 inferno算法

在创建VNode时就确定其类型,以及在mount/patch的过程中采用位运算来判断一个VNode的类型,在这个基础之上再配合核心的Diff算法,使得性能上较Vue2.x有了提升。(实际的实现可以结合Vue3.x源码看。)

该算法中还运用了动态规划的思想求解最长递归子序列