反应diff算法的实现示例

  

  

在上一篇文章,我们已经实现了反应的组件功能,从功能的角度来说已经实现了反应的核心功能了。

  

但是我们的实现方式有很大的问题:每次更新都重新渲染整个应用或者整个组件,DOM操作十分昂贵,这样性能损耗非常大。

  

为了减少DOM更新,我们需要找渲染前后真正变化的部分,只更新这一部分DOM。而对比变化,找出需要更新部分的算法我们称之为diff算法。

  

  

在前面两篇文章后,我们实现了一个渲染方法,它能将虚拟DOM渲染成真正的DOM,我们现在就需要改进它,让它不要再傻乎乎地重新渲染整个DOM树,而是找出真正变化的部分。

  

这部分很多类反应框架实现方式都不太一样,有的框架会选择保存上次渲染的虚拟DOM,然后对比虚拟DOM前后的变化,得到一系列更新的数据,然后再将这些更新应用到真正的DOM上。

  

但也有一些框架会选择直接对比虚拟DOM和真实DOM,这样就不需要额外保存上一次渲染的虚拟DOM,并且能够一边对比一边更新,这也是我们选择的方式。

  

不管是DOM还是虚拟DOM,它们的结构都是一棵树,完全对比两棵树变化的算法时间复杂度是O (n ^ 3),但是考虑到我们很少会跨层级移动DOM,所以我们只需要对比同一层级的变化。

  

 diff反应算法的实现示例“> <br/>
  </p>
  <p>只需要对比同一颜色框内的节点</p>
  <p>总而言之,我们的diff算法有两个原则:</p>
  <ol>
  <李>对比当前真实的DOM和虚拟DOM,在对比过程中直接更新真实DOM李</>
  <李>只对比同一层级的变化实现李</>
  </ol>
  <p>我们需要实现一个diff方法,它的作用是对比真实DOM和虚拟DOM,最后返回更新后的DOM </p>
  
  <pre类=/* *   * @param HTMLElement} {dom真实dom   * @param {vnode} vnode虚拟DOM   * @returns HTMLElement}{更新后的DOM   */函数diff (dom, vnode) {//?   }      

接下来就要实现这个方法。
  

  

在这之前先来回忆一下我们虚拟DOM的结构:
  

  

虚拟DOM的结构可以分为三种,分别表示文本,原生DOM节点以及组件。

     //原生DOM节点的vnode   {   标签:“div ',   attrs: {   名称:“容器”   },   孩子们:[]   }//文本节点的vnode   “你好,世界”//组件的vnode   {   标签:ComponentConstrucotr,   attrs: {   名称:“容器”   },   孩子们:[]   }      

  

首先考虑最简单的文本节点,如果当前的DOM就是文本节点,则直接更新内容,否则就新建一个文本节点,并移除掉原来的DOM。

     //diff文本节点   如果(typeof vnode==='字符串'){//如果当前的DOM就是文本节点,则直接更新内容   如果(dom,,dom。nodeType===3) {//nodeType: https://developer.mozilla.org/zh-CN/docs/Web/API/Node/nodeType   如果(dom)。textContent !==vnode) {   dom。textContent=vnode;   }//如果DOM不是文本节点,则新建一个文本节点DOM,并移除掉原来的   其他}{=文档。createTextNode (vnode);   如果(dom,,dom。parentNode) {   dom.parentNode。方法(dom);   }   }      返回;   }      

文本节点十分简单,它没有属性,也没有子元素,所以这一步结束后就可以直接返回结果了。

  

  

如果vnode表示的是一个非文本的DOM节点,那就要分几种情况了:
  

  

如果真实DOM和虚拟DOM的类型不同,例如当前真实DOM是一个div,而vnode的标记的值是“按钮”,那么原来的div就没有利用价值了,直接新建一个按钮元素,并将div的所有子节点移到按钮下,然后用方法的方法将div替换成按钮。

        如果(! dom | | dom.nodeName.toLowerCase () !==vnode.tag.toLowerCase ()) {=文档。createElement (vnode。标签);      如果(dom) {   […dom。子节点的时候存在缺陷)。(地图。列表末尾);//将原来的子节点移到新节点下      如果(dom)。parentNode) {   dom.parentNode。方法(dom);//移除掉原来的DOM对象   }   }   }      

如果真实DOM和虚拟DOM是同一类型的,那我们暂时不需要做别的,只需要等待后面对比属性和对比子节点。

  

  

实际上diff算法不仅仅是找出节点类型的变化,它还要找出来节点的属性以及事件监听的变化。我们将对比属性单独拿出来作为一个方法:

反应diff算法的实现示例