iis服务器助手广告广告
返回顶部
首页 > 资讯 > 前端开发 > VUE >怎么实现一个虚拟DOM算法
  • 519
分享到

怎么实现一个虚拟DOM算法

2024-04-02 19:04:59 519人浏览 独家记忆
摘要

这篇文章主要讲解了“怎么实现一个虚拟DOM算法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么实现一个虚拟DOM算法”吧!即虚拟DOM的diff算法的主体

这篇文章主要讲解了“怎么实现一个虚拟DOM算法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么实现一个虚拟DOM算法”吧!

即虚拟DOM的diff算法的主体思路是:

1.将虚拟DOM结构转化为真实的DOM结构替换到旧的DOM(第一次旧的为undefined),渲染到页面中。

2.当状态变化的时候,新渲染一颗虚拟DOM树和原来旧的虚拟DOM树对比,对比之后记录下差异。

3.将最终由差异的部分转化成真实DOM结构渲染到页面上。

实现

在旧的虚拟节点和新的虚拟节点的对比过程中会出现以下几种情况,下面我们以Vue为例看Vue2.0是Diff算法是怎么实现的:

比较两个元素的标签

如果标签不一样的话直接替换掉,例如:div变成p

div->p  <<<<<<<HEAD <p>前端简报</p>  =========  <div>前端简报</div> >>>>>>>>

判断虚拟节点的tag属性是否相等,如果不相等将新的虚拟DOM树转化为真实DOM结构把原来节点替换掉

if (oldVnode.tag != vnode.tag) {   return oldVnode.el.parentNode.replaceChild(createElm(vnode), oldVnode.el); }

效果图:

怎么实现一个虚拟DOM算法

比较两个元素的文本

当标签一样的时候比较文本是否一样。如果文本不一样的话那么直接替换掉文本内容。

<<<<<<<HEAD <div>前端</div> ========= <div>简报</div> >>>>>>>>

两个节点的tag都是div,故比较孩子虚拟DOM树的是否一样,孩子的tag为undefined说明是文本节点,此时比较本文内容text是否一致即可

if (!oldVnode.tag) {     //文本的对比     if (oldVnode.text != vnode.text) {       return (oldVnode.el.textContent = vnode.text);     }   }

效果图:

怎么实现一个虚拟DOM算法

比较标签属性

如果两个标签一样那么比较标签的属性,当属性更新的时候通过新旧属性的对比会出现下面几种情况:

1、属性对比

如果旧的虚拟节点有,新的虚拟节点没有那么需要删除旧的虚拟节点上的属性。

let newProps = vnode.data || {}; //新的属性 let el = vnode.el; //老的有 新的没有 需要删除属性 for (let key in oldProps) {   if (!newProps[key]) {     el.removeAttribute(key); //移除真实dom的属性   } }

反过来,如果旧的虚拟节点没有,新的虚拟节点有那么直接设置新的属性即可

//新的有 那就直接用新的去更新即可 for (let key in newProps) {     el.setAttribute(key, newProps[key]); }

2、样式处理

如果老的样式中存在新的样式没有那么删除老的样式。

  1. - style={color:red} 

  2. + style={background:red} 


let newStyle = newProps.style || {}; let oldStyle = oldProps.style || {}; //老的样式中有的 新的没有  删除老的样式 for (let key in oldStyle) {   if (!newStyle[key]) {     el.style[key] = "";   } }

相反如果老的样式没有,新的样式存在那么直接更新新的样式即可

for (let key in newProps) {   if (key == "style") {     for (let styleName in newProps.style) {       el.style[styleName] = newProps.style[styleName];     }   }  }
  • 对应的源码地址:src\platforms\web\runtime\modules\style.js

3、类名处理

对于类名处理我们使用新节点的类名

  1. - class="title ant-title" 

  2. + class="title ant-mian-title" 


for (let key in newProps) {  if (key == "class") {     el.className = newProps.class; }
  • 对应的源码地址src\platforms\web\runtime\modules\class.js

比较儿子

在比较儿子的过程中可以分为以下几种情况:

1、老节点有儿子,新节点没有儿子删除老节点的儿子即可

if (isDef(oldCh)) {   removeVnodes(oldCh, 0, oldCh.length - 1) }  ========================================= if (oldChildren.length > 0) {      el.innerhtml = ""; }

2、老节点没有儿子,新节点有儿子遍历children转化为真实的DOM结构添加到页面中

if (isDef(ch)) {   if (isDef(oldVnode.text)) nodeOps.setTextContent(elm, '')   addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue) }  =============================================================== if (newChildren.length > 0) {   for (let i = 0; i < newChildren.length; i++) {      let child = newChildren[i];      el.appendChild(createElm(child));   } }

3、老节点有儿子,新节点有儿子

当老节点的儿子和新节点的儿子都存在并且不相等的时候,这种情况比较复杂也是diff算法的核心。

在vue2.0中比较老节点和新节点区别的时候采用了双指针的方式,通过同时向同一个方向循环老节点和新节点,只要有一个节点循环完成就结束循环。如果是老节点先结束,那么将新节点剩余的元素添加到渲染列表;如果是新节点先结束,那么将旧节点剩余的元素删除即可。

定义开头指针其中包括老节点的开始位置和结束位置,新节点的开始位置和结束位置。

let oldStartIndex = 0; //老的索引  let oldStartVnode = oldChildren[0]; //老的索引指向的节点  let oldEndIndex = oldChildren.length - 1;  let oldEndVnode = oldChildren[oldEndIndex];   let newStartIndex = 0; //新的索引  let newStartVnode = newChildren[0]; //新的索引指向的节点  let newEndIndex = newChildren.length - 1;  let newEndVnode = newChildren[newEndIndex];

通过判断两个节点的key和tag是否相等来确定同一元素

function sameVnode (a, b) {   return (     a.key === b.key && (       (         a.tag === b.tag &&         ...       ) || (         ...       )     )   ) }

正序排列

如果多余的节点的右边的话,那么从左往右依次判断老的开始节点和新的开始节点是否是同一节点,如果是同一节点调用patchVode方法去递归子节点,将老节点和新节点的下标加1向右移动,直到下标大于children的长度。

怎么实现一个虚拟DOM算法
if (sameVnode(oldStartVnode, newStartVnode)) {   patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)   oldStartVnode = oldCh[++oldStartIdx]   newStartVnode = newCh[++newStartIdx] }

效果图:

怎么实现一个虚拟DOM算法

如果是新节点多余添加到渲染视图,如上图从左到右对比时,g节点的下一个el是null,insertBefore相当于appendChild方法向后插入;如果是从右向左,g节点的下一个el是a,那么采用insertBefore相当于向a前面插入节点。

if (oldStartIndex > oldEndIndex) {      for (let i = newStartIndex; i <= newEndIndex; i++) {       let ele =         newChildren[newEndIndex + 1] == null           ? null           : newChildren[newEndIndex + 1].el;       parent.insertBefore(createElm(newChildren[i]), ele);     } }

如果是老节点多余,那么说明这些节点是不需要的,删除掉即可,如果在删除的过程中出现null,说明这个节点已经处理过了跳过即可。

if(newStartIdx > newEndIdx){   for (let i = oldStartIndex; i <= oldEndIndex; i++) {      let child = oldChildren[i];      if(child!= undefined){        parent.removeChild(child.el);      }   } }

如果多余的节点在左边,从新老节点的结束节点开始下标依次减1

if (sameVnode(oldEndVnode, newEndVnode)) {   patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)   oldEndVnode = oldCh[--oldEndIdx]   newEndVnode = newCh[--newEndIdx] }

反转排列

如果遇到新老节点反转的情况,通过老节点的开始节点和新节点的结束节点作对比或者老节点和结束节点和新节点的开始节点作对比。

怎么实现一个虚拟DOM算法

如果老节点的开始节点和新节点的结束节点是同一节点,那么将老的开始节点插入到老的结束节点的下一个节点之前,然后依次分别向右向左移动节点对应的下标,获取对应的值继续遍历。

if (sameVnode(oldStartVnode, newEndVnode)) { // Vnode moved right   patchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue, newCh, newEndIdx)   canMove && nodeOps.insertBefore(parentElm, oldStartVnode.elm, nodeOps.nextSibling(oldEndVnode.elm))   oldStartVnode = oldCh[++oldStartIdx]   newEndVnode = newCh[--newEndIdx] }

如果老节点的结束节点和新节点的开始节点是同一节点吗,那么将老节点的结束节点插入到老节点的开始节点前面,然后依次分别向左向右移动节点对应的下标,获取对应的值继续遍历。

if (sameVnode(oldEndVnode, newStartVnode)) { // Vnode moved left   patchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue, newCh, newStartIdx)   canMove && nodeOps.insertBefore(parentElm, oldEndVnode.elm, oldStartVnode.elm)   oldEndVnode = oldCh[--oldEndIdx]   newStartVnode = newCh[++newStartIdx] }

毫无关系排列

如果在对比的过程中儿子之间没有任何的关系,通过从新节点的开始节点开始依次和老节点的所有节点作对比,如果没有相同的就创建新的节点插入的老节点的开始节点之前,如果在循环的过程中找到了相同的元素,那么直接复用老元素,将和新节点相同的老节点插入到老节点的开始节点之前,为了防止数组的塌陷问题,将移走的老节点的位置设为undefined,最后将多余的老节点全部删除即可。

怎么实现一个虚拟DOM算法

设置缓存组使用老节点的key和下标做一个映射表,新节点的key去老的映射表里筛选,如果没有筛选到,那么就不复用直接创建新节点插入到老节点的开始节点之前。

function createKeyToOldIdx (children) {   let i, key   const map = {}    children.forEach((item, index) => {       if (isDef(item.key)) {         map[item.key] = index; //{a:0,b:1,c:2,d:3,e:4,f:5,g:6}       }   return map }

如果在老节点中找到,那么移动老节点到老节点开始节点之前

let map = createKeyToOldIdx(oldChildren);  //儿子之间没有关系 let moveIndex = map[newStartVnode.key];  //拿到开头的虚拟节点的key去老的里面找  if(moveIndex == undefined){   parent.insertBefore(createElm(newStartVnode),oldStartVnode.el); }else{   let moveVNode = oldChildren[moveIndex];  //这个老的虚拟节点需要移动   oldChildren[moveIndex] = null;   parent.insertBefore(moveVNode.el,oldStartVnode.el);   patch(moveVNode,newStartVnode)  //比较属性和儿子 } newStartVnode = newChildren[++newStartIndex]  //用新的不停的去老的里面找

在移动的过程中开始指针和结束指针可能存在指向null的情况,如果指向null的话那么无法在进行比较,可以直接跳过,指向下一个元素即可。

if (isUndef(oldStartVnode)) {   oldStartVnode = oldCh[++oldStartIdx] // Vnode has been moved left } else if (isUndef(oldEndVnode)) {   oldEndVnode = oldCh[--oldEndIdx] }

源码地址:src/core/vdom/patch.js

为什么要使用key?

人丑话不多先看图

怎么实现一个虚拟DOM算法

有key

怎么实现一个虚拟DOM算法

没有key

如上图所示,第一个图为有key的情况,第二个图为没有key的情况,可以很明显的看到所展示内容如果有key的话,复用了key为A,B,C,D的4个节点,结果只是将新创建的E节点插入到C节点的前面完成渲染。如果没有key的话,那么创建了E,C,D三个节点,降低了复用率,性能方面肯定没有有key  的情况高。

为什么不能用index作为key呢?

平时开发过程中,如果只是通过页面静态渲染是可以使用index作为key的,如果在页面上有复杂的逻辑变化,那么使用index作为key相当于没有key。

<li index=0>A</li>      <li index=0>C</li> <li index=1>B</li>      <li index=1>B</li> <li index=2>C</li>      <li index=2>A</li>

如上代码所示,将下标为0和2的A和C变换位置之后需要重新创建节点A和C,此时C的下标为0,A的下标为2。而以id或者唯一标识作为key的话,相当于是将A和C元素的位置进行平移。平移的性能比创建节点的性能高。

在使用index作为key的时候还会产生意想不到的问题,假如我们把B节点删除,我们最开始取值为B,现在取值变成了C。

感谢各位的阅读,以上就是“怎么实现一个虚拟DOM算法”的内容了,经过本文的学习后,相信大家对怎么实现一个虚拟DOM算法这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

--结束END--

本文标题: 怎么实现一个虚拟DOM算法

本文链接: https://www.lsjlt.com/news/83922.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • 怎么实现一个虚拟DOM算法
    这篇文章主要讲解了“怎么实现一个虚拟DOM算法”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“怎么实现一个虚拟DOM算法”吧!即虚拟DOM的diff算法的主体...
    99+
    2024-04-02
  • 怎么在react中实现一个虚拟dom和diff算法
    这篇文章将为大家详细讲解有关怎么在react中实现一个虚拟dom和diff算法,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。虚拟DOM,见名知意,就是假的DOM,我们真实的DOM挂载在页面上...
    99+
    2023-06-14
  • 怎么在Vue中实现一个虚拟dom
    怎么在Vue中实现一个虚拟dom?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、什么是虚拟dom?虚拟dom本质上就是一个普通的JS对象,用于描述视图的界面结构在vue中...
    99+
    2023-06-14
  • 深入聊一聊虚拟DOM与diff算法
    目录虚拟DOM与diff算法snabbdom环境搭建虚拟DOM和h函数diff算法patch函数patchVnode函数updateChildren函数v-for中key作...
    99+
    2024-04-02
  • Vue中的虚拟DOM和Diff算法实例分析
    这篇文章主要介绍了Vue中的虚拟DOM和Diff算法实例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Vue中的虚拟DOM和Diff算法实例分析文章都会有所收获,下面我们一起来看看吧。简单介绍一下 虚拟DO...
    99+
    2023-06-29
  • javascript的虚拟DOM怎么进化为真实DOM
    本篇内容主要讲解“javascript的虚拟DOM怎么进化为真实DOM”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“javascript的虚拟DOM怎么进化为真...
    99+
    2024-04-02
  • Vue虚拟Dom到真实Dom的转换方法
    这篇文章主要介绍“Vue虚拟Dom到真实Dom的转换方法”,在日常操作中,相信很多人在Vue虚拟Dom到真实Dom的转换方法问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue虚拟Dom到真实Dom的转换方法...
    99+
    2023-06-20
  • react中的虚拟dom和diff算法详解
    虚拟DOM的作用 首先我们要知道虚拟dom的出现是为了解决什么问题的,他解决我们平时频繁的直接操作DOM效率低下的问题。那么为什么我们直接操作DOM效率会低下呢? 比如我们创建一个d...
    99+
    2024-04-02
  • Vue的虚拟DOM和diff算法你了解吗
    目录什么是虚拟DOM?为什么需要虚拟DOM?总结在vue 中 数据改变 -> 虚拟DOM(计算变更)-> 操作DOM -> 视图更新 虚拟DOM: js执行速度比较...
    99+
    2024-04-02
  • vue如何实现虚拟dom的patch
    这篇文章主要为大家展示了“vue如何实现虚拟dom的patch”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“vue如何实现虚拟dom的patch”这篇文章吧。具...
    99+
    2024-04-02
  • React之虚拟DOM的实现原理
    目录React虚拟DOM机制React diff 算法1. 传统 diff 算法2. react diff 算法总结最后React虚拟DOM机制 虚拟DOM本质上是JavaScrip...
    99+
    2023-01-16
    React 虚拟DOM 虚拟DOM 虚拟DOM实现原理
  • 怎么实现一个random shuffle算法
    这篇文章主要介绍“怎么实现一个random shuffle算法”的相关知识,小编通过实际案例向大家展示操作过程,操作方法简单快捷,实用性强,希望这篇“怎么实现一个random shuffle算法”文章能帮助大家解决问题。...
    99+
    2023-06-30
  • vue中虚拟DOM与Diff算法知识精讲
    目录前言虚拟DOM(Virtual DOM):什么是虚拟DOM为什么要使用虚拟DOM:虚拟dom库diff算法snabbdom的核心init函数h函数patch函数(核心)diff算...
    99+
    2024-04-02
  • 怎样深入理解vue中的虚拟DOM和Diff算法
    怎样深入理解vue中的虚拟DOM和Diff算法,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。真实DOM的渲染在讲虚拟DOM之前,先说一下真实DOM的渲染。浏览器真实DOM渲...
    99+
    2023-06-22
  • C++中怎么实现一个 kmp算法
    本篇文章给大家分享的是有关C++中怎么实现一个 kmp算法,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。C++ kmp算法模板参数说明const T *source 待匹配的字...
    99+
    2023-06-17
  • Java中怎么实现一个TFIDF算法
    这篇文章给大家介绍Java中怎么实现一个TFIDF算法,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。算法介绍最近要做领域概念的提取,TFIDF作为一个很经典的算法可以作为其中的一步处理。计算公式比较简单,如下:预处理由...
    99+
    2023-06-02
  • 使用Unity怎么实现一个虚拟键盘功能
    使用Unity怎么实现一个虚拟键盘功能?相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。具体内容如下using UnityEngine;using System...
    99+
    2023-06-09
  • 怎么在react中实现一个diff算法
    这期内容当中小编将会给大家带来有关怎么在react中实现一个diff算法,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。单节点Diff单节点Diff比较简单,只有key相同并且type相同的情况才会尝试复用...
    99+
    2023-06-14
  • 怎么在java中实现一个gc算法
    这期内容当中小编将会给大家带来有关怎么在java中实现一个gc算法,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。Java可以用来干什么Java主要应用于:1. web开发;2. Android开发;3. ...
    99+
    2023-06-14
  • PHP中怎么实现一个排序算法
    PHP中怎么实现一个排序算法,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。< //插入排序(一维数组)  function ins...
    99+
    2023-06-17
软考高级职称资格查询
推荐阅读
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作