【虚拟DOM】【key值】 【DOM diff】

目录

虚拟DOM

操作DOM

VDOM建模

差量更新

DOM diff【判断DOM发生了变化,并找到这个变化】

虚拟DOM的优点

key值的用处


虚拟DOM

DOM:将文档解析为一个由节点和对象(包含属性和方法的对象)组成的结构集合。

VDOM:也叫虚拟DOM,它是仅存于内存中的DOM,因为还未展示到页面中,所以称为VDOM。

var a = document.createElement("div");

如上,就是一个VDOM。

如果让VDOM变成真实的DOM呢?其实很简单,只需将节点append到页面中

var a = document.createElement("div");
document.body.append(a);

Virtual DOM 其实就是一棵以 JavaScript 对象 (VNode 节点) 作为基础的树,用对象属性来描述节点,实际上它只是一层对真实 DOM 的抽象。最终可以通过一系列操作使这棵树映射到真实环境上。

在 JavaScript 里面,虚拟 DOM 表现为一个 Object 对象。并且最少包含标签名 (tag)、属性 (attrs) 和子元素对象 (children) 三个属性。

不过总的来讲,Virtual DOM 对象的节点跟 DOM Tree 每个位置的属性一一对应的,因为人们创造出虚拟 DOM 就是为了更好地将虚拟节点渲染到视图上,也就是把虚拟DOM变成真实的 DOM 节点,提高视图的渲染性能。

操作DOM

我们日常中常见的DOM操作有哪些?

事实上,就三类:增、删、改。对应的DOM操作如下:

  1. 增加一个节点 => appendChild
  2. 删除一个节点 => removeChild
  3. 更改一个节点 => replaceChild

VDOM建模

说是建模,简单点说就是用一个JS对象来表示VDOM。

如果我们可以用一个JS对象来表示VDOM,那么这个对象上多一个属性(增加节点),少一个属性(删除节点),或者属性值变了(更改节点),就一目了然了!

比如这一段 HTML 代码对应的 DOM:

hello
world

节点无非都是由以下三部分组成:

  1. tag : 元素类型
  2. attrs : 元素属性
  3. children : 子元素集合

我们用另外的一个对象来表示它:

let nodesData = {
  tag: 'div',
  children: [
    {
      tag: 'div',
      children: [
        {
          tag: 'span',
          children: [
            {
              tag: '#text',
              text: 'hello'
            }
          ]
        }
      ]
    },
    {
      tag: 'span',
        children: [
          {
            tag: '#text',
            text: 'world'
          }
        ]
    }
  ]
}

用这个对象来表示 DOM 结构,我们可以根据这个对象来构建真正的 DOM。

现在我们需要写一个函数,将这个虚假的 DOM 转化为真实的 DOM。

function vNode({tag, children, text}){
  this.tag = tag
  this.children = children
  this.text = text
}

vNode.prototype.render = function(){
  if(this.tag === '#text'){
    return document.createTextNode(this.text)
  }
  
  let el = document.createElement(this.tag)
  this.children.map((vChild) => {
    el.appendChild(new vNode(vChild).render())
  })
  
  return el
}

调用上面的这个函数可以将我们用来表示 DOM 的对象(虚假 DOM)变成真正的 DOM。

let node = new vNode(nodesData)
node.render()

这样,就化假 DOM 为真 DOM 了。

当我们的需要改变 DOM 时,只需要改变其对应的虚假 DOM,再调用一下 render 函数,就可以改变真实 DOM,不需要我们亲自用 JavaScript 去操作页面中的 DOM。

差量更新

上面虽然实现了从虚假 DOM 到真实 DOM 的转化,但是也有一个问题,那就是每次转化都会遍历所有的 DOM 结构,通通的全部转化一遍。如果只有一个小地方发生了改变,也需要将全部的 DOM 更新一遍,那这样就太耗费性能了,我们应该比较虚假 DOM 的变化,只更新变化的地方。比如你加了一个节点,那么我就只更新这个节点,我无需整个模板替换。这样一来,效率就提高了。

DOM diff【判断DOM发生了变化,并找到这个变化】

DOM diff 即比较两颗虚拟 DOM 树区别的算法。

diff 算法仅在两个树的同级的虚拟节点之间做比较,递归地进行比较,最终实现整个 DOM 树的更新。

diff 算法主要包括几个步骤:

  • 用 JS 对象的方式来表示 DOM 树的结构,然后根据这个对象构建出真实的 DOM 树,插到文档中。
  • 当状态变更的时候,重新构造一棵新的对象树。然后用新的树和旧的树进行比较,记录两棵树的差异
  • 最后把所记录的差异应用到所构建的真正的DOM树上,视图更新

通过递归children的方式,就可以判断不同的children并对其操作。有以下几种情况:

  1. 没有旧的节点,则创建新的节点,并插入父节点。
  2. 如果没有新的节点,则摧毁旧的节点。
  3. 如果节点发生了变化,则用replaceChild改变节点信息
  4. 如果节点没有变化,则对比该节点的子节点进行判断,使用递归调用

虚拟DOM的优点

  • 减少 DOM 操作的次数

虚拟 DOM 的一个优点表现在它可以减少 DOM 操作。

在一个

    下面有1000个
  • ,如果改变了其中10个,如果没有虚拟 DOM,简单直接的做法就是整个
      替换掉重新渲染,这里面其实就存在了很多不必要的 DOM 操作。

      DOM 操作慢是慢在浏览器渲染的过程里,改变一行数据就要全部重新渲染,在大多数情况下虚拟 DOM 比 DOM 快,是因为需要更新的 DOM 节点要比原生 DOM 操作更新的节点少,浏览器重绘的时间更短。而且虚拟 DOM 的优势不在于单次的操作,用对比的算法,它可以将多次操作合并成一次操作,在大量、频繁的数据更新下,能够对视图进行合理、高效的更新。

      • 减少DOM操作的范围

      虚拟DOM借助DOM diff可以把多余的操作省掉,比如添加1000个div,通过对比区分出哪些是新增的、哪些是重复的,如果只有10个是新增的就只渲染这10个。

      • 跨平台

      虚拟 DOM 的另一个优点就是可以跨平台。

      由于虚拟 DOM 是以 JavaScript 对象为基础的,它的本质就是一个 JavaScript 对象,并不依赖真实平台环境,所以使它具有了跨平台的能力。它在浏览器上可以变成 DOM,在其他平台里也可以变成相应的渲染对象。

      key值的用处

      key的作用主要是为了高效的更新虚拟DOM。

      diff算法是从左往右进行同层级对比的,如果发现元素相同但是内容不相同,会直接修改内容。解决的方法就是加上唯一的 key,让 Diff 知道就算是同类型的组件,也是有名字区分的,更新视图就不会出错。

      两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构。

      同一层级的一组节点,他们可以通过唯一的id进行区分。

      • 首先,在Vue中,存在一个DOM复用机制,会尽量的回收DOM元素进行复用,而这个机制本身是高效的,但很多时候也会造成不可预知的Bug,而在加了key值后,元素就有了一个标识,复用机制不会复用带key值的元素。而React也存在类似的机制。

      • 反之,若使用相同的key值,可以使组件复用,也有可能导致渲染内容缺失。

      • 因此,key值一般来说,最好是独一无二的。

      • 除此之外,虚拟DOM在使用Diff算法进行对比时,若存在key值,可以更高效更迅速。

      一:如果没有key值,就会根据就地复用的原则,一个一个对比,然后修改渲染,场景:在同一层级的某一堆节点中插入一个新节点。如果有key,diff算法就可以通过对比找到正确的位置插入新节点,而key值相同的dom节点就不要去比较。
      二:如果key值用index,假如我在数组中间插入一项的时候,此时从这一项开始的key值就全部都变了,都需要重新对比渲染。因此,复杂的列表不要用index。

你可能感兴趣的:(vue)