vue笔记3 虚拟dom、重流、diff

老规矩 先放链接致敬作者

使用虚拟dom和真实dom的区别
详细理解重绘与回流/重排的过程
display:none和visibility:hidden两者的区别
v-if和v-show、display: none和visibility:hidden的区别
vue核心之虚拟DOM(vdom)
深入 React 之 diff 算法
vue学习——vue中的diff 算法
神秘的diff算法(vue)
vue中使用v-for时为什么不能用index作为key?

虚拟dom

真实DOM操作:
document.get…查询的是整个节点树。
ParentNode.querySelector()和ParentNode.querySelectorAll()是有范围地查询ParentNode下的节点,过程中是需要根据传入的参数来比对节点上的属性。普通的真实dom作增删改时会引起浏览器的重排和重绘

虚拟DOM操作:
this.$refs.refName查询的是当前组件实例上的属性$refs对象中key为refName的属性。现在几大主流框架都实现了虚拟dom,并且实现了新旧节点比较,这就解决了以前多次重排重绘的问题,且减少了代码量

真实DOM操作:真实DOM增删改 + (可能较多节点)重排与重绘
虚拟DOM操作:虚拟DOM增删改 + 真实DOM差异增删改(这与Diff算法效率有关) + (较少节点)重排与重绘

tips:重排(回流) 和重绘

注意:回流必将引起重绘,而重绘不一定会引起回流。回流会导致渲染树需要重新计算,开销比重绘大,所以我们要尽量避免回流的产生
回流的产生:
1.页面第一次渲染 在页面发生首次渲染的时候,所有组件都要进行首次布局,这是开销最大的一次回流。
2.浏览器窗口尺寸改变
3.元素位置和尺寸发生改变的时候
4.新增和删除可见元素
5.内容发生改变(文字数量或图片大小等等)
6.元素字体大小变化。
7.激活CSS伪类(例如::hover)。
8.设置style属性
9.查询某些属性或调用某些方法。比如说:
offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
除此之外,当我们调用getComputedStyle方法,或者IE里的currentStyle时,也会触发回流,原理是一样的,都为求一个“即时性”和“准确性”。

重绘的产生:
当render tree中的一些元素需要更新属性,而这些属性只是影响元素的外观,风格,而不会影响布局的,比如
visibility、outline、背景色等属性的改变。

下面的代码是影响回流和重绘的:

var s = document.body.style;
s.padding = "2px"; // 回流+重绘
s.border = "1px solid red"; // 再一次 回流+重绘
s.color = "blue"; // 重绘
s.backgroundColor = "#ccc"; // 重绘
s.fontSize = "14px"; // 再一次 回流+重绘
document.body.appendChild(document.createTextNode('abc!'));// 添加node,再一次 回流+重绘

v-show v-if

v-if是动态的向DOM树内添加或者删除DOM元素
v-show本质是利用标签的display属性控制显隐
v-if="false"在DOM不能获取到该标签
v-show=false在DOM中仍能获取到该标签

真实DOM和其解析流程(浏览器)

创建DOM树——创建StyleRules——创建Render树——布局Layout——绘制Painting
第一步,用HTML分析器,分析HTML元素,构建一颗DOM树(标记化和树构建)。
第二步,用CSS分析器,分析CSS文件和元素上的inline样式,生成页面的样式表。
第三步,将DOM树和样式表,关联起来,构建一颗Render树(这一过程又称为Attachment)。每个DOM节点都有attach方法,接受样式信息,返回一个render对象(又名renderer)。这些render对象最终会被构建成一颗Render树。
第四步,有了Render树,浏览器开始布局,为每个Render树上的节点确定一个在显示屏上出现的精确坐标。
第五步,Render树和节点显示坐标都有了,就调用每个节点paint方法,把它们绘制出来。

DOM树的构建是文档加载完成开始的?构建DOM树是一个渐进过程,为达到更好用户体验,渲染引擎会尽快将内容显示在屏幕上。它不必等到整个HTML文档解析完毕之后才开始构建render数和布局。

Render树是DOM树和CSSOM树构建完毕才开始构建的吗?这三个过程在实际进行的时候又不是完全独立,而是会有交叉。会造成一边加载,一遍解析,一遍渲染的工作现象。

CSS的解析是从右往左逆向解析的(从DOM树的下-上解析比上-下解析效率高),嵌套标签越多,解析越慢。
在这里插入图片描述
用我们传统的开发模式,原生JS或JQ操作DOM时,浏览器会从构建DOM树开始从头到尾执行一遍流程。在一次操作中,我需要更新10个DOM节点,浏览器收到第一个DOM请求后并不知道还有9次更新操作,因此会马上执行流程,最终执行10次。例如,第一次计算完,紧接着下一个DOM更新请求,这个节点的坐标值就变了,前一次计算为无用功。计算DOM节点坐标值等都是白白浪费的性能。

现在: 将dom树转化为js代码(虚拟dom)然后操作虚拟dom 转化为真实dom 其中操作虚拟dom用到的diff算法
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
Snabbdom Vue.js2.x内部使用的虚拟DOM就是改造的Snabbdo

diff算法

传统 diff 算法

采用两个组件递归的方式。时间复杂度为 O(n^3)。其中 n 为子节点数。也就是如果有 100 个节点。需要执行 1000000 次。传统的diff算法计算一棵树变成另一棵的复杂度是O(n^3)。
编辑距离(edit distance)Wiki的解释是:edit distance is a way of quantifying how dissimilar two strings (e.g., words) are to one another by counting the minimum number of operations required to transform one string to the other。=> 我的理解是从一个东西变成另一个东西的最少步骤。传统的diff算法里,从一棵树变成另一棵的需要的最少步骤,解决这个问题(tree edit distance)的时间复杂度是O(n^3),怎么来的呢?

两棵树中的节点一一进行对比的复杂度为O(n^2),树1上的点1要遍历树2上的所有的点,树1上的点2也要遍历树2的所有点,以此类推,复杂度为O(n^2)。如果在比较过程中发现树1(也就是旧树)上的一个点A在树2(新树)上没有找到,点A会被删掉,在老diff算法里点A被删后的空位,需要遍历树2上的所有点去找到一个可以填充它,复杂度为O(n)。

总结一下,对于旧树上的点A来说,它要和新树上的所有点比较,复杂度为O(n),然后如果点A在新树上没找到的话,点A会被删掉,然后遍历新树上的所有点找到一个去填空,复杂度增加为了O(n^2),这样的操作会在旧树上的每个点进行,最终复杂度为O(n^3)。

React diff 算法

三大策略
React diff 算法是对树的每一层进行遍历,如果节点没有就删除节点。对于不同类型的节点,直接替换。对于相同类型的节点,使用 shouldComponentUpdate 进行判断。React 中采用三大策略将时间复杂度为 O(n^3)优化到 O(n)。
1、跨层级的 DOM 操作忽略不计。
2、拥有相同类型的组件节点生成相似的树形结构,拥有不用类型的组件节点生成不同的树形结构。
3、对于同一层级的节点,用唯一 id 区别。

diff 算法两个核心
1、在组件更新的时候执行。
2、寻找差异,局部更新。

三大组件更新
以上说到 diff 算法的核心就是组件更新。组件类型分为三种,分别为文本元素、自定义元素和 DOM元素。
文本元素更新:直接更新替换文本,不会调用 diff 算法。如图文本是单独作为一个节点的
自定义元素更新:同类型的元素,继续比较下去。不同类型的元素直接替换。
DOM 元素更新:对于属性差异部分,直接更新属性,对于子节点属性差异部分,直接改变子节点属性。对子节点更新,找出节点的差异,更新差异部分的 DOM 元素。

基于 diff 算法的优化建议
当 DOM 跨层级操作时,只会删除或创建节点。因此有可能整个节点树都会被重新创建。所以尽量不要跨层级添加或移除操作,可以通过 CSS 显示/隐藏节点。
使用 shouldComponentUpdate() 来减少组件不必要的更新。
开发组件时保持 DOM 结构的稳定性,尽量不要动态操作 DOM,尤其是移动 DOM。
尽量避免将最后一个列表节点移动到首位的操作。(react)

vue diff算法

vue和react的diff算法的区别

vue和react的diff算法,都是忽略跨级比较,只做同级比较。vue diff时调动patch函数,参数是vnode和oldVnode,分别代表新旧节点。

  1. vue比对节点,当节点元素类型相同,但是className不同,认为是不同类型元素,删除重建,而react会认为是同类型节点,只是修改节点属性。
  2. vue的列表比对,采用从两端到中间的比对方式,而react则采用从左到右依次比对的方式。当一个集合,只是把最后一个节点移动到了第一个,react会把前面的节点依次移动,而vue只会把最后一个节点移动到第一个。总体上,vue的对比方式更高效。
  3. Vue进行diff时,调用patch打补丁函数,一边比较一边给真实的DOM打补丁。

不能用index作为key的原因

首先Vue 和 React 的虚拟DOM的Diff算法大致相同,其核心是基于两个简单的假设:

1、两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构。
2、同一层级的一组节点,他们可以通过唯一的id进行区分。当页面的数据发生变化时,Diff算法只会比较同一层级的节点:

如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点。
如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。

使用key来给每个节点做一个唯一标识,Diff算法就可以正确的识别此节点,找到正确的位置去插入新的节点。
不能用index:比如删掉中间一个元素,index会跟着改变,key也跟着变,导致重新渲染,浪费性能。

总结

直接操作dom代价大,采用虚拟dom,为提升虚拟dom效率,使用diff算法
第一次生成虚拟dom反而会慢,但后面更新时候快
复杂视图情况下提升渲染性能,因为虚拟DOM+Diff算法可以精准找到DOM树变更的地方,减少DOM的操作(重排重绘)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值