(十一)虚拟DOM一定比真实DOM快吗

一、虚拟DOM

1、虚拟dom理解:

虚拟Dom(Virtual-dom简称vdom)通过使用js来模拟真实的dom。它可以让我们在js 中创建、更新和删除DOM元素。所以我们可以精准的改变我们需要改变的真实DOM,而不是每一次改变都改变整个DOM,从而减少DOM开销。另外,这种通过js来控制dom 的方式就可以实现数据来改变结构。

2、使用虚拟DOM的原因:

浏览器的工作流程:构建DOM树->创建CSSOM->运行JS->形成渲染树->实现布局->绘制页面
直接使用原生的api 或者jquery操作dom 浏览器会从构建DOM树开始从头到尾执行一遍。DOM 操作非常昂贵,而js的效率更高。可以用js来模拟DOM结构。从而减少DOM的操作。

3、DOM操作对性能的影响:重绘与重排

重排(Reflow):当DOM的变化影响了元素的几何属性(宽、高、位置)等,浏览器需要重新计算页面布局,并调整其他元素的位置。这个过程称为重排,它比较耗时,尤其是在处理复杂布局时。
重绘(Repaint):当DOM的变化只影响元素的外观,而不影响其布局时(如改变颜色、背景颜色、字体颜色)浏览器只需要重新绘制受影响的区域,而不需要进行整个页面的布局计算。重绘通常比重排快,但频繁的重绘依然会影响性能。

4、 频繁DOM操作导致的问题

频繁的DOM操作,尤其是触发重排和重绘的操作,会显著降低页面的渲染性能。用户可能会遇到页面卡顿、动画不流畅问题。甚至大量的操作还会增加浏览器的内存消耗。可能导致页面崩溃或者响应缓慢。

5、虚拟dom是如何实现的

snabbdom实现虚拟dom的核心:h()、patch()、diff算法

h()函数的作用是将js模拟的DOM结构,转换成虚拟DOM结构,再通过patch()函数将虚拟DOM转换成真实的DOM结构渲染到页面中。

patch()函数分为两个阶段:
一、虚拟DOM首次渲染。二、更新DOM节点。

diff 函数接收两个参数,分别是旧的虚拟 DOM 树和新的虚拟 DOM 树,它会返回一个表示差异的对象。patch 函数接收三个参数,分别是真实的 DOM 节点、旧的虚拟 DOM 树和表示差异的对象。patch 函数会根据差异对象来更新真实的 DOM 节点。

一个简单的DOM diff算法的实现思路:
遍历旧的虚拟DOM树和新的虚拟DOM树,将它们的每个节点进行比较。
如果节点类型不同,直接替换旧的节点。
如果节点类型相同,比较节点属性是否有变化,如果有变化,更新节点属性。
如果节点有子节点,递归执行步骤1-3。
如果旧的虚拟DOM树比新的虚拟DOM树多出节点,直接删除多余的节点。
如果新的虚拟DOM树比旧的虚拟DOM树多出节点,直接添加新的节点。
实现DOM diff算法的关键在于建立虚拟DOM树,虚拟DOM树可以用JS对象来表示一个真实的DOM树,通过比较新旧虚拟DOM树的节点属性和子节点,可以判断出需要更新、删除或添加的节点。

diff.js

let Index = 0;
// diff算法
function diff(oldTree, newTree) {
  let patches = {};
  let index = 0;
  walk(oldTree, newTree, index, patches);
  return patches;
}
// 对比新老节点的变化,将变化的内容存到补丁包里面
function walk(oldTree, newTree, index, patches) {
  let currentPatch = [];
  if (!newTree) {
    currentPatch.push({ type: "REMOVE", index });
  } else if (isString(oldTree) && isString(newTree)) {
    if (oldTree !== newTree) {
      currentPatch.push({ type: "TEXT", text: newTree });
    }
  } else if (oldTree.type === newTree.type) {
    let attrs = diffAttr(oldTree.props, newTree.props);
    console.log(attrs);
    if (Object.keys(attrs).length > 0) {
      currentPatch.push({ type: "ATTR", attrs });
    }
    diffChildren(oldTree.children, newTree.children, patches);
  } else {
    currentPatch.push({ type: "REPLACE", newTree });
  }

  if (currentPatch.length > 0) {
    console.log(index);
    patches[index] = currentPatch;
  }
}
// 判断是否为字符串,如果为字符串直接渲染
function isString(obj) {
  return Object.prototype.toString.call(obj) === "[object String]";
}
// 节点比较
function diffChildren(oldChildren, newChildren, patches) {
  oldChildren.forEach((child, idx) => {
    walk(child, newChildren[idx], ++Index, patches);
  });
}

// 节点属性设置
function diffAttr(oldAttrs, newAttrs) {
  let patch = {};
  for (let key in oldAttrs) {
    if (oldAttrs[key] !== newAttrs[key]) {
      patch[key] = newAttrs[key];
    }
  }

  for (let key in newAttrs) {
    if (!oldAttrs.hasOwnProperty(key)) {
      patch[key] = newAttrs[key];
    }
  }
  return patch;
}

patch.js

let allPatches;
let index = 0;
// diff对比完毕,使用patch进行补丁渲染
function patch(node, patches) {
  console.log(patches);
  allPatches = patches;
  walk(node);
}
// 查找是否有补丁,如果有进行替换
function walk(node) {
  let currentPatch = allPatches[index++];
  let childNodes = node.childNodes;
  childNodes.forEach((child) => walk(child));
  if (currentPatch) {
    doPatch(node, currentPatch);
  }
}
// 补丁替换
function doPatch(node, patches) {
  patches.forEach((patch) => {
    switch (patch.type) {
      case "ATTR":
        for (let key in patch.attrs) {
          let value = patch.attrs[key];
          if (value) {
            setAttr(node, key, value);
          } else {
            node.removeAttribute(key);
          }
        }
        break;
      case "TEXT":
        node.textContent = patch.text;
        break;
      case "REPLACE":
        let newNode =
          patch.newTree instanceof Element
            ? render(patch.newTree)
            : document.createTextNode(patch.newTree);
        node.parentNode.replaceChild(newNode, node);
        break;
      case "REMOVE":
        node.parentNode.removeChild(node);
        break;
      default:
        break;
    }
  });
}
// 如果补丁为属性时进行替换
function setAttr(node, key, value) {
  console.log(node, key, value);
  switch (key) {
    case "value":
      if (
        node.tagName.toUpperCase() === "INPUT" ||
        node.tagName.toUpperCase() === "TEXTAREA"
      ) {
        node.value = value;
      } else {
        node.setAttribute(key, value);
      }
      break;
    case "style":
      node.style.cssText = value;
      break;
    default:
      node.setAttribute(key, value);
      break;
  }
}
export { patch };



二、Diff算法

  • React:
    逐层进行节点比较:
    不同节点类型比较:(1)节点类型不同 (2)节点类型相同,但是属性不同。
    当树中的两个不同类型的节点,react直接删除前面的节点,然后创建并插入新的节点。(需要注意的是,删除节点意味着彻底销毁该节点,而不是再后续的比较中再去看是否有另外一个节点等同于该删除的节点。如果该删除的节点之下有子节点,那么这些子节点也会被完全删除,它们也不会用于后面的比较。这也是算法复杂能够降低到 O(n)的原因。)React 的 DOM Diff 算法实际上只会对树进行逐层比较。
    相同类型节点比较:react会对属性进行重设从而实现节点的转换。
    同层节点比较(列表节点比较):如果未提供key,那么react将认为是对应位置组件类型不同,因此完全删除后重建。如果列表节点存在唯一key,可以帮助react 快速定位到正确的节点进行比较,从而减少DOM操作,提高了性能。

vue和react的diff算法比较:
1、vue对比节点,当元素相同,但是classname不同,认为是不同类型的元素,会删除重建,而react认为是相同类型的节点,只是修改节点属性。
2、vue的列表对比,采用的两端到中间的对比,而react 采用从左到右的对比方式。当一个集合只是把最后一个节点移到了第一个,react会把前面的节点一次移动,而vue只会把最后一个节点移动到第一个。总体来说vue比较高效。

三、虚拟DOM一定比真实DOM快吗

不一定。比如首次渲染或者所有节点都需要进行更新的时候,采用虚拟DOM会比直接操作更新原生DOM多一些重构虚拟DOM的动作,因此占用了更多的内存,延长了渲染时间。

参考文章:
https://zhuanlan.zhihu.com/p/63836801
https://blog.csdn.net/adminmijinlin/article/details/128485071?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171202473916800185851436%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=171202473916800185851436&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-1-128485071-null-null.142v100pc_search_result_base1&utm_term=%E8%99%9A%E6%8B%9Fdom%E4%B8%80%E5%AE%9A%E6%AF%94%E7%9C%9F%E5%AE%9Edom%E5%BF%AB%E5%90%97&spm=1018.2226.3001.4187
https://blog.csdn.net/weixin_42224055/article/details/126483126?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171202473916800225523853%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=171202473916800225523853&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allfirst_rank_ecpm_v1~rank_v31_ecpm-3-126483126-null-null.142v100pc_search_result_base1&utm_term=%E8%99%9A%E6%8B%9Fdom%E4%B8%80%E5%AE%9A%E6%AF%94%E7%9C%9F%E5%AE%9Edom%E5%BF%AB%E5%90%97&spm=1018.2226.3001.4187
https://blog.csdn.net/qq_32594913/article/details/135556290
https://blog.csdn.net/qq_41887214/article/details/130279091

https://blog.csdn.net/qq_45777332/article/details/130674964

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值