关于vue.js:Vfor就地复用原理虚拟DOMDiff算法

49次阅读

共计 3958 个字符,预计需要花费 10 分钟才能阅读完成。

V-for 就地复用原理


举个🌰:

<div v-for="(item,index) in items">
  <input />
  <button @click="del(index)">delthis</button>
  {{item.message}}
</div>

JS 局部

//data 外面的 items
items: [{  message: "1"},
  {message: "2"},
  {message: "3"},
  {message: "4"},
],
//methods 中的 del 办法
del(index) {this.items.splice(index, 1); // 依据传入的 index 删掉 items 中对应数据
},

成果如下



能够发现:

  • 当删掉 items 中的第二个对象时,输入框中的值还是 2 – 这意味着 没有删除对应的第二个节点。这是因为 vue 采纳虚构 DOM+diff 算法导致的数据凌乱。
  • vue 监听到 items 数组中少了个元素后,会更新虚构 DOM,而后应用 diff 算法比拟新、旧 DOM 树,在这个过程中,因为要计算出实在 DOM 树的 最小变更规模 ,因而会尽可能 复用已有的节点(如果节点类型雷同)

    此处,咱们的需要当然是不复用节点,那该如何实现呢?

    :key 解决 v -for 导致的数据凌乱

  • 在渲染列表时,为每个元素绑定 举世无双的 key,这样,vue 在更新经 v -for 渲染过的列表时,因为 key 值不同,会认为是不同的节点类型,不采取复用。这样就防止了数据凌乱

为什么不能应用数组下标作为 key:

  • 不能应用各元素的 index 作为 key,因为当新增或删除列表中元素时,各项索引 都会变,也就是说索引对应元素变了,失去了标识的唯一性

    申明式渲染


Vue 提供一套基于 HTML 的模板语法,容许开发者申明式地将实在 DOM 与 Vue 实例的数据绑定在一起

“ 申明式 ” 的意思就是: 只须要指出指标, 而不必关怀如何实现,将实现交由 vue 解决

虚构 DOM


  • Vdom(virtual dom), 能够看作是一个 应用 javascript 模仿了 DOM 构造的树形构造

    • 其中 Vnode 节点对应实在 DOM 节点
  • Vdom 树 用于缓存实在 DOM 树的所有信息

    为什么要采纳虚构 DOM?

所有为了性能

“间接操作 DOM 性能差”,这是因为 ——

  1. DOM 引擎 JS 引擎 互相独立,但又工作在同一线程(主线程),因而 JS 代码调用 DOM API 时必须 挂起 JS 引擎、激活 DOM 引擎,实现后再转换到 JS 引擎
  2. 引擎间切换的代价 会迅速积攒
  3. 强制 重排 DOM API调用,哪怕只改变一个节点,也会 引起整个 DOM 树重排 从新计算布局 从新绘制图像 会引起更大的性能耗费

所以,升高引擎切换频率 (缩小 DOM 操作次数) 减小 DOM 变更规模 才是 DOM 性能优化 的两个关键点。


虚构 DOM +diff 算法是一种可选的解决方案

基本思路:“在 JS 中 缓存 必要数据,计算 界面更新时的 数据差别 ,只 提交 最终 差集”。

  • 虚构 dom 只用于缓存,而
  • diff 算法负责 –

    • 计算出‘虚构 dom 和目前实在 DOM 之间的数据差别
    • 提交最终差集

    留神:“单纯 VDOM 是进步不了性能的,VDOM 次要作用在于它的二次形象提供了一个 diff/patch 和 batch commit(批量提交)的机会”

watcher 的节流成果:借助 watcher 响应式原理,使数据异步更新(滞后更新),可能实现节流成果,在一段时间内,容许屡次更新虚构 DOM,而后一次性 patch 到实在 DOM 树。像是应用精灵图以缩小申请次数那样,达到优化性能的目标。

vue 在监听到数据变动后,会将依赖该数据的 watcher 退出微工作队列,因为微工作是异步的,因而所有同步更新数据的操作,都会及时地在微工作队列中的工作更新前触发 watcher 响应,换个说法:执行第一次变动后的每次变动都会更新 watcher 中的各项依赖。这样的话,在该微工作执行结束之前的这段时间,就相当于节流中的时延了

Vdom 的 Diff 算法


diff 算法的两个外围:

  1. 两个雷同的组件产生相似的 DOM 构造,不同的组件产生不同的 DOM 构造。
  2. 同一层级的一组节点,他们能够通过惟一的 key 进行辨别。

diff 算法的复杂度

  • 比拟两棵虚构 DOM 树的差别 是 Virtual DOM 算法最外围的局部,这也是所谓的 VirtualDOM 的 diff 算法。两个树的齐全的 diff 算法是一个工夫复杂度为 O(n^3) 的问题。
  • 然而在前端当中,你很少会逾越层级地挪动 DOM 元素。所 diff 算法只会对同一个层级的元素进行比照。上面的 div 只会和同一层级的 div 比照,第二层级的只会跟第二层级比照。这样算法复杂度就能够达到O(n)

    比拟时是否复用的逻辑

当页面的数据发生变化时,Diff 算法只会比拟同一层级的节点:

  • 如果 节点类型不同,间接干掉旧的节点,创立并插入新的那个节点,不会再比拟这个节点当前的子节点了。
  • 如果 节点类型雷同,则会间接复用该节点,从新设置该节点的属性,从而实现节点的更新。

    当某一层有很多雷同的节点时,也就是列表节点时,Diff 算法的更新过程默认状况下也是遵循以上准则。

比方 – 咱们心愿能够在 B 和 C 之间加一个 F

Diff 算法默认执行起来是这样的:

  • 老的 Vdom 树的该层上有 6 个节点,新的 Vdom 树上有 7 个类型雷同的节点,那么就顺次复用实在 DOM 树该层上的对应的前 6 个节点,在最初再新建一个节点,赋予之前节点 E 的属性。
  • 即把 C 更新成 F,D 更新成 C,E 更新成 D,最初再插入 E,是不是很没有效率?

所以咱们须要应用 key 来给每个节点做一个惟一标识,这样 vue 会把他们当做是不同的节点,因而不会复用,diff 算法会 间接创立新的节点,并插入正确的地位

key 的作用

  • key 的作用次要是为了高效的更新虚构 DOM。
  • 也可防止间接复用 v -for 进去的节点,防止数据凌乱
  • 另外 vue 中在应用 雷同标签名元素的过渡切换 时,也会应用到 key 属性,其目标也是为了让 vue 能够辨别它们,否则 vue 只会替换其外部属性而不会触发过渡成果。

patch 到实在 DOM

模仿实现

如何将 vnode(右边)变成实在的 DOM 元素(左边)

实现如下:

let nodes = {
  tag: "ul",
  attrs: {id: "list",},
  children: [
    {
      tag: "li",
      attrs: {class: "item",},
      children: ["Item 1"],
    },
  ],
};
// 实现办法:递归遍历
function createElement(vnode) {
  var tag = vnode.tag;
  var attrs = vnode.attrs || {};
  var children = vnode.children || [];
  if (!tag) {return null;}
  var elem = document.createElement(tag);
  var attrName;
  for (attrName in attrs) {if (attrs.hasOwnProperty(attrName)) {elem.setAttribute(attrName, attrs[attrName]);
    }
  }
  for (let i = 0; i < children.length; i++) {let childVnode = children[i];
    if (typeof childVnode === "object" ||
      childVnode.constructor === Object
    ) {elem.appendChild(createElement(childVnode));
    } else {let text = document.createTextNode(childVnode);
      elem.appendChild(text);
      break;
    }
  }
  return elem;
}
let elem = createElement(nodes);
console.log(elem);

PS:

vue 在 patch 时,在一个 update 办法外面调用 createElment()办法,通过虚构节点创立实在的 DOM 并插入到它的父节点中;

相当于 打补丁 到实在 DOM


🌰

最初,举个栗子梳理一下:

让 Vue 将 name 的数据和 <p> 标签绑定在一起:

<p>Hello {{name}}</p>

让咱们梳理一下 vue 对这个节点 p 和数据所做的所有

  • Vue 会把这些模板编译成一个 渲染函数 render
  • 该函数被调用后会渲染并且返回一个 虚构的 DOM 树. 这个 “ 树 ” 的职责就是形容以后视图应处的状态。
  • 之后再通过一个Patch 函数,计算和旧虚构 dom 树的差集,并通过打补丁的形式将差集中的虚构节点更新到实在 DOM 树。
  • 在整个过程中, Vue 借助 数据劫持和订阅者模式 实现监听状态、依赖收集、依赖追踪告诉变动等。会侦测在渲染过程中所依赖到的数据起源,以实现双向绑定,自动更新状态。

参考:

理解一下 v -for 原理

Vue2.0 v-for 中 :key 到底有什么用?

Vue–patch | 学 Vue 看这个就够了

https://www.zhihu.com/question/324992717/answer/690011952

vue 考点 —— Diff 算法

既然用 virtual dom 能够进步性能,为什么浏览器不间接自带这个性能呢?– 水歌 | 知乎

你不晓得的 React 和 Vue 的 20 个区别【面试必备】

vue diff 算法 patch

diff 算法中的概念

正文完
 0