Vue2.0变化侦测

一、虚拟DOM

为什么要引入:访问DOM是非常昂贵的,会造成相当多的性能浪费。

引入前:Vue.js 1.0中,当状态发生变化时,它在一定程度上知道哪些节点使用了这个状态,从而对这些节点进行更新操作,根本不需要比对。但因为粒度太细,每一个绑定都会有一个对应的watcher 来观察状态的变化,这样就会有一些内存开销以及一些依赖追踪的开销。当状态被越多的节点使用时,开销就越大。

引入后:Vue.js 2.0中,组件级别是一个watcher 实例,就是说即便一个组件内有10个节点使用了某个状态,但其实也只有一个watcher在观察这个状态的变化。所以当这个状态发生变化时,只能通知到组件,然后组件内部通过虚拟DOM去进行比对与渲染。这是一个比较折中的方案。

模板转换成视图的过程:模板描述状态与DOM之间的映射关系。Vue.js通过编译将模板转换成渲染函数(render),执行渲染函数就可以得到一个虚拟节点树,使用这个虚拟节点树就可以渲染页面。

虚拟DOM的执行流程:(1)提供与真实DOM节点所对应的虚拟节点vnode。(2)将虚拟节点vnode和旧虚拟节点oldVnode进行比对,然后更新视图。

 

二、VNode的类型

VNode的类型:注释节点、文本节点、元素节点、组件节点、函数式组件、克隆节点

三、patch

1、patch的作用:对比两个VNode,修改DOM节点

2、patch的过程:新增节点、删除节点、修改节点

(1)新增节点:

  1. 当oldVnode不存在,直接使用vnode创建元素并渲染视图
  2. 当oldVnode和vnode完全不是同一个节点时,需要使用vnode生成真实的DOM元素并插入视图

(2)删除节点:当oldVnode和vnode完全不是同一个节点时,需要使用vnode生成真实的DOM元素并插入到旧节点的旁边,并将旧节点删除。

(3)更新节点:当oldVnode和vnode是同一个节点时,使用更详细的对比操作对真实的DOM节点进行更新。

3.创建节点

只有三种类型的节点会被创建并插入到DOM中: 元素节点、注释节点、文本节点

(1)元素节点 :若有tag属性则为元素节点,使用createElement方法创建(递归创建子节点),使用appendChild插入到父节点。

(2)注释节点:若isComment属性为true,则为注释节点,使用createComment创建。

(3)文本节点:当tag属性不存在,并且isComment不为true时,则为文本节点,使用createTextNode创建。

         

 

4、更新节点

(1)静态节点:不会因为数据的改变而改变的节点,不需要更新节点如

<p>我是静态节点</p>

(2)新vnode有文本属性时,无论旧节点中是文本节点还是元素节点,都调用setTextContent,将视图中的DOM节点的内容 改为 vnode的text属性所保存的文字。

(3)新vnode是元素节点时

1.若有子节点

  • oldVnode也有子节点,则进行比较并更新
  • oldVnode没有子节点,则清空变成空标签再将vnode中的children属性挨个创建成真实的DOM元素节点并插入视图中的DOM节点下。

2.若无子节点,说明是个空节点,将oldVnode中的内容(元素或文本)删除,最后达到视图中是空标签的目的。

 

5、更新策略

(1)创建子节点插入的位置是所有未处理节点的前面,而不是所有处理节点的后面,因为vnode是和oldVnode比较的,如果处理第一步时在真实DOM中添加了一个新节点,再执行第二步时oldVnode中是看不到刚才添加的新节点,所以如果是将这一步的新节点添加在所有处理节点的后面,则会添加在第一步新增节点的前面。这是不对的。

6、判断哪些节点是未处理过的

所有的对比都是针对未处理的节点的,已处理过的节点忽略不计。那么,怎么分辨哪些节点是处理过的,哪些节点是未处理过的呢?

我们的逻辑都是在循环体内处理的,由于前面我们的优化策略,不能从前向后循环,而应该是从两边向中间循环。

准备4个变量:oldStartIdx 、oldEndIdx 、newStartIdx 和newEndIdx 。4个变量分别表示oldChildren 的开始位置的下标(oldStartIdx )和结束位置的下标(oldEndIdx ),以及newChildren 的开始位置的下标(newStartIdx )和结束位置的下标(newEndIdx )。

在循环体内,每处理一个节点,就将下标向指定的方向移动一个位置,通常情况下是对新旧两个节点进行更新操作,就相当于一次性处理两个节点,将新旧两个节点的下标都向指定方向移动一个位置。

开始位置所表示的节点被处理后,就向后移动一个位置;结束位置的节点被处理后,则向前移动一个位置。

当开始位置大于等于结束位置时,说明所有节点都遍历过了,则结束循环:

while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
    // 做点什么
}

 当新子节点和旧子节点的节点数量不一致时,会导致循环结束后仍然有未处理的节点。

  • 如果是oldChildren 先循环完毕,这个时候如果newChildren 中还有剩余的节点(下标在newStartIdx 和newEndIdx 之间),说明这些节点都是需要新增的节点,直接把这些节点插入到DOM中就行了,不需要循环比对了。
  • 如果是newChildren 先循环完毕,这时如果oldChildren 还有剩余的节点(下标在oldStartIdx 和oldEndIdx 之间),这说明oldChildren 中剩余的节点都是被废弃的节点,是应该被删除的节点。这时不需要循环对比就可以知道需要将这些节点从DOM中移除。

7、更新子节点的整体流程

{%}

 

 

 

 

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值