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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。