dom diff原理,vue虚拟domdiff算法

  dom diff原理,vue虚拟domdiff算法

  为什么vue的这些mvvm框架等等。比传统的dom渲染更快?下面文章主要介绍关于虚拟DOM和diff算法的相关信息,通过示例代码详细介绍。有需要的朋友可以参考一下。

  

目录

  虚拟dom和diff算法snabbdom环境构建虚拟DOM和h函数diff算法补丁函数patchVnode函数updateChildren函数v-for关键函数和原理总结

  

虚拟DOM与diff算法

  在vue、react等技术出现之前,每次修改DOM都需要遍历查询DOM树,找到需要更新的DOM,然后修改样式或结构,造成了严重的资源损失。对于虚拟DOM来说,DOM的每一次变化都变成了JS对象属性的变化,可以很容易的发现JS对象属性的变化,而且性能开销比查询DOM树要少,所以可以提高浏览器的性能。

  对于vue,从vue2开始就支持虚拟DOM。

  Diff算法:简单来说就是找出两个对象之间的差异,只更新有差异的一小块DOM,而不是整个DOM,以达到最小的更新效果。

  虚拟DOM:代码段将在内部被解析成一个对象(真实的DOM通过模板被编译成一个虚拟DOM)

  JS对象用来描述DOM的层次结构,DOM中的所有属性在虚拟DOM中都有对应的属性。

  

snabbdom环境搭建

  它是虚拟DOM库,diff算法的鼻祖,vue源代码借鉴snabbdom。

  官方Git:3359github.com/snabbdom/snabbdom

  git上snabbdom的源代码是用TypeScript写的。如果想直接使用编译好的Javascript版本的snabbdom库,可以从npm下载npm I-D snabbdom。

  snabbdom库是一个dom库,不能在node js环境下运行。需要构建webpack和webpack-dev-server开发环境。注意,必须安装webpack@5。

  NPM I-D web pack @ 5 web pack-CLI @ 3 web pack-dev-server @ 3

  参考官网:https://webpack.docschina.org/配置webpack.config.js文件

  const path=require( path );

  模块.导出={

  //入口

  条目:。/src/index.js ,

  //导出

  输出:{

  //虚拟打包路径,不会实际生成文件夹,而是在8080端口虚拟生成。

  公共路径: xuni ,

  //打包的文件名

  文件名: bundle.js ,

  },

  //配置webpack-dev-server

  devServer: {

  //端口号

  端口:8082,

  //静态根目录

  内容库:“www”,

  },

  }

  通过修改项目根目录下package.json文件中脚本的配置,可以通过npm run dev启动项目。

  配置完成后,将测试官网中的示例。由于示例想要获得id=container的节点,我们需要提前准备一个id为container的div。

  !声明文档类型

  html lang=en

  头

  meta charset=UTF-8

  标题标题/标题

  /头

  身体

  div id=容器/div

  script src=/xuni/bundle . js /script

  /body

  /html

  注意事项

  页面的以下状态表示配置已完成

  

虚拟DOM和h函数

  差异发生在虚拟DOM上

  在新的虚拟DOM和旧的虚拟DOM之间进行Diff(精细比较),并计算出如何以最小的量更新它,并最终反映在真实的DOM上。

  h函数用于生成虚拟节点(vnode)

  H (a ,{ props:{ href: https://www . Baidu . com } }, Baidu );

  会得到这样一个虚拟节点

  {sel: a , data :{ props:{ href: 3359 www . Baidu . com } }, text: Baidu}

  它表示真正的DOM节点。

  a href= 3359 www . Baidu . com Baidu/a

  如果需要让虚拟节点爬树,就需要使用patch函数。

  导入{

  初始化,

  classModule,

  推进模块,

  样式模块,

  eventListenersModule模块,

  h,

  }来自‘snabbdom’;

  //创建一个补丁函数

  var patch=init([classModule,propsModule,styleModule,eventListenersModule]);

  //创建虚拟节点

  Varnode1=h (a ,{ props:{ href: 3359 www . Baidu . com } }, Baidu );

  //让虚拟节点沿树向上

  const container=document . getelementbyid( container );

  补丁(容器,vnode 1);

  h函数可以嵌套得到虚拟DOM树。

  h(ul ,{},[

  h(李,{},可乐);

  h(李,{},雪碧);

  h(李,{},椰汁);

  ])

  

diff算法

  实现最小量的更新。需要钥匙。Key是这个节点的惟一标识符,告诉diff算法它们在更改前后是同一个DOM节点。

  只有相同的虚拟节点用于精确比较。否则删除旧的插入新的就是暴力。

  相同的虚拟节点:相同的选择器和相同的键

  只会做同层比较,不会做跨层比较。

  比如下面两个DOM节点,虽然是同一个虚拟节点,但是即使跨层,还是会暴力删除旧的,插入新的。

  const vnode1=h(div ,{},[

  h(p ,{ key: A }, A ),

  h(p ,{ key: B }, B ),

  h(p ,{ key: C }, C ),

  h(p ,{ key: D }, D ),

  ]);

  const vnode2=h(div ,{},h(section ,{},[

  h(p ,{ key: A }, A ),

  h(p ,{ key: B }, B ),

  h(p ,{ key: C }, C ),

  h(p ,{ key: D }, D ),

  ]))

  分析源代码也可以验证以上。

  首先,我们会判断它是否是一个虚拟节点,如果不是,我们会先将其打包成一个虚拟节点。

  然后判断是否是同一个节点,如果不是,插入新的删除旧的,如果是,细化比较。

  执行流程图

  

patch函数

  首先判断oldVnode是不是虚拟节点,如果是DOM节点,就把oldVnode封装成虚拟节点。

  然后判断新节点和旧节点是否是同一个节点,键值是否相同,标签名称是否相同,数据是否定义(数据包含一些特定的信息,onclick,style等。)

  如果不是同一个节点,新节点直接替换旧节点,删除旧节点,插入新节点。在源代码中,创建所有子节点时需要递归。

  如果新旧节点是同一个节点,将执行patchVnode来比较子节点。

  

patchVnode函数

  首先会找到对应的真实DOM。

  const elm=(vnode . elm=old vnode . elm)!

  如果新旧节点相同,直接返回if(oldVnode===vnode)返回。

  如果vnode没有文本节点(isUndef(vnode.text))

  他们都有孩子,各不相同。

  比较子代(diff update children(diff算法的核心)

  只有vnode有子节点。

  那么oldVnode就是一个空标签或者文本节点。如果是文本节点,清空文本节点,然后创建vnode的子节点作为一个真正的DOM,并将其插入到空标签中。

  只有oldVnode有孩子。

  Vnode没有的东西需要在oldVnode中删除(所有old vnode有和vnode没有的东西都需要清除或删除)

  只有oldVnode有文本。

  空文本

  如果vnode具有文本属性并且不同

  以Vnode为标准,不管oldVnode是什么类型的节点,都直接设置为vnode中的文本。

  

updateChildren函数

  updateChildren方法的核心是:

  提取新旧节点的子节点:新节点子节点ch和旧节点子节点oldCh。

  和oldCh分别设置StartIdx(头指针)和EndIdx(尾指针)变量,并相互比较。此时,有四个变量:oldStartIdx、oldEndIdx、newStartIdx和newEndIdx(这里采用了双指针的思想)

  有四种方法进行比较:

  oldStartIdx和newStartIdx的比较

  如果有匹配,则不需要修改DOM,oldStartIdx和newStartIdx的下标后移一位。

  oldEndIdx和newEndIdx的比较

  如果有匹配,则不需要修改DOM,oldEndIdx和newEndIdx的下标向前移动一位。

  oldStartIdx和newEndIdx的比较

  如果匹配,则不需要修改DOM,将oldStartIdx对应的真实DOM插入到最后一位,oldStartIdx的下标后移一位,newEndIdx的下标前移一位。

  oldEndIdx和newStartIdx的比较

  如果存在匹配,则不需要修改Dom。在oldEndIdx对应的实DOM前插入oldEndIdx对应的实DOM,oldEndIdx的下标前移一位,newStartIdx的下标后移一位。

  如果四种方法都不匹配成功,如果设置了键,将通过键进行比较。在比较过程中,startIdx,endIdx -,一旦StartIdx EndIdx表示至少遍历了ch或oldCh中的一个,此时比较将结束。

  处理后,如果还有新的节点,就添加;如果还有旧的节点,删除它们。

  

v-for中key作用与原理

  Key是虚拟DOM对象的标识符。当数据发生变化时,Vue会根据新的数据生成一个新的虚拟DOM,然后Vue会比较新的虚拟DOM和旧的虚拟DOM的差异。比较规则如下:

  (1)在旧虚拟DOM中发现了与新虚拟DOM相同的关键字。

  如果虚拟DOM中的内容包含

  如果虚拟DOM中的内容发生变化,则生成新的真实DOM,然后替换页面中之前的真实DOM。

  (2)在旧虚拟DOM中找不到与新虚拟DOM相同的键。

  创建一个新的真实DOM,然后将其呈现到页面上。

  所以,如果你使用索引作为键,可能出现的问题是:

  (1)如果对数据进行逆序添加、逆序删除等顺序操作,会发生不必要的真实DOM更新==界面效果还好,但是效率低。

  (2)如果结构中还包含输入类的DOM,会产生错误DOM error==接口有问题。

  实际开发中如何选择key

  最好使用每条数据的唯一标识符作为密钥,如id、手机号、身份证号、学号等。如果没有逆序添加或删除数据等破坏性的顺序操作,只用于渲染列表显示,使用index作为键没有问题。

  

总结

  这就是这篇关于虚拟DOM和diff算法的文章。关于虚拟DOM和diff算法的更多信息,请搜索我们以前的文章或继续浏览下面的相关文章。希望你以后能支持我们!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: