2.vue的diff算法(2020.12.07)
在之前的生命周期中有提到过虚拟dom的相关概念,这里来简单介绍一个vue的diff算法的原理
1.virtual dom
无论是vue还是react,在更新渲染的过程中,都是先根据真实dom生成一个虚拟dom对象,如图下所示
当某个节点发生变化时,vue都会根据新的vnode与oldVnode进行对比,如果不一样,就会直接修改在真实dom树。
这里要说一点,对于简单的dom结构,有时候直接修改真实dom的效率要比加一层virtual dom效率摇号,但是在项目庞大复杂的背景下,virtual dom拥有绝对的优势,它避免了很多没必要的操作。夸张的举个例子,比方说在一颗有上万个子节点且多层的dom树上,一旦一个子节点发生了改变,除非我们直接获取该节点(但是实际情况上我们做不到给每一个子节点添加标记,例如id和ref等等),那这个时候,在virtual dom上操作显然是方便许多,避免了很多在真实dom上遍历的操作。因此,其实virtual dom在很多并不是一个最优解,但是它绝对是最普适的。
2.diff算法
既然实质是比较两个对象的异同,我们关心的是如何进行比较,下图就很好解释了比较的过程,同层但不跨层(react和vue的共同之处)
举一个具体的例子
按照从上到下的比较过程,其实我们发现在第二层的时候,就已经有些不同了,这里其实就很容易看出来,我们只要把移到
后面就可以完成更新了。但是记住,比较的过程是同层不能跨层,所以diff操作在第2层会移除掉 ,并在第3层的时候,添加一个。
在vue中,diff算法的实质其实是调用了patch函数,一边比较一边打补丁,哪里不一样补哪里。
我们很容易的看出patch函数有两个参数oldVnode和vnode,也就是旧新节点,我们来看一下Vnode这个对象
el所对应的就是真实dom节点,但是在vnode对象里,el的值为null,因为其还并没有对应的真实dom。
现在来解析一下patch函数
逻辑很明确,如果新旧节点相等(注意这里指的是key和sel相等,也就是类型(例如p和span)和类(样式),这和react中的逻辑不同),那他俩就值得比较,所以我们就执行patchVnode函数,以此来比较他们的子节点是否相同(逐层)
1.const el = vnode.el = oldVnode.el ,让vnode.el引用到现在的真实dom,当el修改时,vnode.el会同步变化。
2.if(oldVnode ===vnode),如果她俩引用一致,那就直接return掉
3.如果它俩的text不同,就更新text
4.否则,调用updateEle(el,vnode,oldVnode),更新el节点
下面三步就是
1.如果它俩都有子节点且它不同,那我们就要执行updateChildren(),更新他们的子节点
2.如果vnode有,OldVnode没有,则将Vnode的子节点真实化之后添加到el
3.如果OldVnode有,vnode没有,则删除OldVnode的子节点。
2.如果不一样,则执行
我们先保存OldVnode的父节点(真实dom),然后createEl(vnode),为vnode创建他的真实dom,也就是vnode.el = 真实dom。然后就将新真实dom插入,移除旧真实dom。
最后,return vnode,就将有真实dom的vnode返回,其在下一次就会被当做oldVnode了。
这样就简单介绍完了,感谢
https://segmentfault.com/a/1190000008782928
https://www.cnblogs.com/wind-lanyan/p/9061684.html
等一下!!!!!!!!!!!!!!!!
是不是还有啥没讲,对!没错就是updateChildren,diff算法的精髓所在,在之前我只是把他一笔带过,是因为代码太长且难以一时理解(即便是在看别人的解析下),所以我决定自己去研读,逐行解读,这里也希望大家能够自己去看代码当看不懂解析的时候,用自己的方式去理解。(大可不必去看里面写的注释),这边其实就是给大家一个源码自己去阅读。
(这里偷个图,形象的理解一下)
updateChildren (parentElm, oldCh, newCh) { //传入的值分别是,el,oldChidren,newChildrenlet oldStartIdx = 0, newStartIdx = 0 //设定旧和新的头索引(可以理解为指针)let oldEndIdx = oldCh.length - 1 //设定oldChildren的尾索引,指向最后一个let oldStartVnode = oldCh[0] //设定old开始节点为oldCh[0]let oldEndVnode = oldCh[oldEndIdx] //设定old结束节点为oldCh[oldEndIdx]let newEndIdx = newCh.length - 1 //同理,同上,newCh也这么操作let newStartVnode = newCh[0] let newEndVnode = newCh[newEndIdx]let oldKeyToIdx //设定oldkey值let idxInOld let elmToMovelet before while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {//如果两个头索引都是小于尾索引if (oldStartVnode == null) { // 头节点=null,则++oldStartVnode = oldCh[++oldStartIdx] }else if (oldEndVnode == null) { //尾节点为null,则--oldEndVnode = oldCh[--oldEndIdx]}else if (newStartVnode == null) { //头节点为null,则++newStartVnode = newCh[++newStartIdx]}else if (newEndVnode == null) { //尾节点为null,则--newEndVnode = newCh[--newEndIdx] ------------------------------sameVnode分割线 }else if (sameVnode(oldStartVnode, newStartVnode)) {//如果old和new的两个头结点一样patchVnode(oldStartVnode, newStartVnode)//则将两个头结点带入patchVnode中执行oldStartVnode = oldCh[++oldStartIdx] //头索引++newStartVnode = newCh[++newStartIdx] //头索引++}else if (sameVnode(oldEndVnode, newEndVnode)) { //如果old和new两个尾节点一样patchVnode(oldEndVnode, newEndVnode)//则将两个尾节点带入patchVnode中执行oldEndVnode = oldCh[--oldEndIdx] //尾索引--newEndVnode = newCh[--newEndIdx] //尾索引--}else if (sameVnode(oldStartVnode, newEndVnode)) {//若old头结点===new尾节点patchVnode(oldStartVnode, newEndVnode)//则将头结点和尾节点带入patchVnode中执行//由于是新的尾节点和旧的头节点一样,我们就把真实dom中的第一个节点(oldStartVnode.el)//移到最后api.insertBefore(parentElm, oldStartVnode.el, api.nextSibling(oldEndVnode.el))oldStartVnode = oldCh[++oldStartIdx] //旧头++newEndVnode = newCh[--newEndIdx] //新尾--}else if (sameVnode(oldEndVnode, newStartVnode)) {//若旧尾节点===新头节点patchVnode(oldEndVnode, newStartVnode)//则将旧尾和新头带入patchVnode中执行//在头和尾的操作中,我们要明确一个逻辑,就是旧要跟着新的来//所以这里就是把旧尾所对应的真实dom移到旧头的前面。api.insertBefore(parentElm, oldEndVnode.el, oldStartVnode.el)oldEndVnode = oldCh[--oldEndIdx] //旧尾--newStartVnode = newCh[++newStartIdx] //新头++}else {if (oldKeyToIdx === undefined) {oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx) // 有key生成index表}idxInOld = oldKeyToIdx[newStartVnode.key]if (!idxInOld) {api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)newStartVnode = newCh[++newStartIdx]}else {elmToMove = oldCh[idxInOld]if (elmToMove.sel !== newStartVnode.sel) {api.insertBefore(parentElm, createEle(newStartVnode).el, oldStartVnode.el)}else {patchVnode(elmToMove, newStartVnode)oldCh[idxInOld] = nullapi.insertBefore(parentElm, elmToMove.el, oldStartVnode.el)}newStartVnode = newCh[++newStartIdx]}}}//上述代码,其实就是在移动位置,目的都是改变oldCh中所对应的真实dom的位置,使其对应//newCh的位置,那么,位置确定了,接下来要改的就是打补丁了,多的删掉,少的补上。if (oldStartIdx > oldEndIdx) { //如果旧头大于旧尾(说明oldCh长度是小于等于NewCh),//那么我们需要增加真实dom节点before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].eladdVnodes(parentElm, before, newCh, newStartIdx, newEndIdx)}else if (newStartIdx > newEndIdx) {//反之则删除真实dom节点removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx)}
}
2.vue的diff算法(2020.12.07)相关推荐
- 2.react的diff算法(2020.12.07)
正常情况下,比较两个树形结构的差异的算法的时间复杂度为O(n^3),这个效率显然无法直接接收的,react通过总结DOM的实际使用场景提出了两个在绝大多数实践场景下都成立的假设,基于这两个假设,rea ...
- [vue] 你了解vue的diff算法吗?
[vue] 你了解vue的diff算法吗? 我的理解:计算出虚拟 DOM 中真正变化的部分,并且只针对该部分进行 DOM 更新,而非重新渲染整个页面 个人简介 我是歌谣,欢迎和大家一起交流前后端知识. ...
- Vue中diff算法的理解
Vue中diff算法的理解 diff算法用来计算出Virtual DOM中改变的部分,然后针对该部分进行DOM操作,而不用重新渲染整个页面,渲染整个DOM结构的过程中开销是很大的,需要浏览器对DOM结 ...
- vue的diff算法原理
1. 为什么要用Diff算法 由于在浏览器中操作DOM是很昂贵的,频繁的操作DOM,会产生一定的性能问题,这就是虚拟DOM的产生原因.虚拟DOM本质上是JavaScript对象,是对真实DOM的抽象状 ...
- [Vue源码] Vue中diff算法原理
一. Vue中diff算法原理 理解: 1.先同级比较,在比较子节点 2.先判断一方有儿子一方没儿子的情况 3.比较都有儿子的情况 4.递归比较子节点 图: 1.原节点:ABCD,新节点:ABCDE, ...
- 详解vue的diff算法
前言 目标是写一个非常详细的关于diff的干货,所以本文有点长.也会用到大量的图片以及代码举例,一起来get吧. 先来了解几个点... 1. 当数据发生变化时,vue是怎么更新节点的? 要知道渲染真实 ...
- vue的diff 算法
1. 当数据发生变化时,vue是怎么更新节点的? 要知道渲染真实DOM的开销是很大的,比如有时候我们修改了某个数据,如果直接渲染到真实dom上会引起整个dom树的重绘和重排,有没有可能我们只更新我们修 ...
- Vue之diff算法
前言 Vue通过双向绑定来实现数据驱动视图更新,当数据更新会触发Dep对象notify通知所有的订阅者Watcher对象,Watcher对象最后会调用run来执行Watcher对象的getter方法. ...
- 【vue】diff 算法详解
一.diff算法是什么 diff算法是一种通过同层的树节点进行比较的高效算法 diff算法的目的就是找出新旧不同虚拟DOM之间的差异,使最小化的更新视图,所以 diff 算法本质上就是比较两个js对象 ...
最新文章
- CMake结合PCL库学习(1)
- Tomcat学习总结(2)——Tomcat使用详解
- C# winform 编写记事本
- xml.etree ElementTree简介
- php使用常量cont,php常量介绍
- 什么是hibernate懒加载?什么时候用懒加载?为什么要用懒加载?
- 纪念硕士论文圆满答辩结束——20180614
- linux下组态软件,linux组态软件入门使用
- 实战之8051驱动8位数码管
- oracle查看登录失败次数,Oracle取消用户连续登录失败次数限制
- python 分词 词性_分词及词性标注
- 如何面试大厂web前端?(沟通软技能总结)
- java项目编码问题解决
- python terminal 库_zhihu-terminal 终端版知乎客户端
- 这是一个全民销售的时代
- 【经济学视频课程】科斯定理的本质…
- git删除目录下的所有文件并提交
- 第一课:句子成分与基本句型
- 【NOIP2006PJ】开心的金明(happy)
- 2011年10月大盘下跌大股东增持股票
热门文章
- Android Room 之存储 Objects 中的 List
- linux精灵进程之crond
- centos 7 vs centos6 的不同
- android实战开发02
- UIImagePickerController选择图片发送后旋转90度的问题
- python培训多久能入职_Python学到什么程度可以面试工作?
- 【工程项目经验】mac电脑lldb调试工具
- sublime后缀_提高数据分析工作效率-Sublime如何设置默认打开文件格式
- 编写一个计算机程序用来计算一个文件的 16 位效验和(Java实现)
- javaweb(04) xml