前言

先介绍一个概念Virtual Dom,我猜大家或多或少都应该知道什么是Virtual Dom吧,简单来说就是用js来模拟DOM中的节点。

Virtual Dom

下面就是一个Virtual Dom的结构,包含了标签名,拥有的属性,孩子节点,render函数

class Element {constructor(tagName, attrs, children) {this.tagName  = tagName;this.attrs    = attrs || {};this.children = children || [];}render () {//这个函数是用来生成真实DOM的,最后会把return的结果添加到页面中去 }
}

创建一棵个Virtual Dom Tree && 渲染

/**
<ul id="list"><li class="a">txt_a</li><li class="a">txt_b</li>
</ul>
**/
//根据上面结构可以用一下方式创建一棵 Virtual Dom Tree
let ul = Element('ul', { id: 'list' }, [Element('li', { class: 'a' }, ['txt_a']),Element('li', { class: 'b' }, ['txt_b'])
]);//ul 就是一棵个Virtual Dom Tree
let ulDom = ul.render();//生成真实Dom
document.body.appendChild(ulDom);//添加到页面中

以上就是Virtual Dom Tree如何被转换成真实Dom并添加到网页中的过程,再这个过程中我把render函数给省略,只是为了让你们先了解原理,具体实现可以以后再深究。我学一个东西的时候,习惯是先把整体原理弄清楚,再去深入学习相关的知识。

Diff 算法

在介绍Diff算法之前,再次声明我只会列举Diff算法中会用到的函数,并串联它们之间的关系并不会给出具体实现的代码

介绍

diff算法是进行虚拟节点Element的对比,并返回一个patchs对象,用来存储两个节点不同的地方,最后用patchs记录的消息去局部更新Dom。

两个树如果完全比较的话需要时间复杂度为O(n^3),如果对O(n^3)不太清楚的话建议去网上搜索资料。而在Diff算法中因为考虑效率的问题,只会对同层级元素比较,时间复杂度则为O(n),说白了就是深度遍历,并比较同层级的节点。

Diff只需下面两句代码

  • 判断两棵Virtual Dom Tree 差异
  • 把差异更新到真实Dom中去
let patchs = diff(oldTree, newTree);//获取两棵Virtual Dom Tree 差异
patch(ulDom, patchs);//找到对应的真实dom,进行部分渲染

Diff中所用到的函数

//深度遍历树,将需要做变更操作的取出来
//局部更新 DOM
function patch(node,patchs){//代码略
}// diff 入口,比较新旧两棵树的差异
function diff (oldTree, newTree) {let index   = 0let patches = {} // 记录每个节点差异的补丁dfs(oldTree, newTree, index, patches)return patches;
}
/*** dfs 深度遍历查找节点差异* @param  oldNode - 旧虚拟Dom树* @param  newNode - 新虚拟Dom树* @param  index - 当前所在树的第几层* @param  patches - 记录节点差异*/
function dfs (oldNode, newNode, index, patches){let currentPatch = [];//当前层的差异对比if (!newNode) {//如果节点不存不用处理,listdiff函数会处理被删除的节点}else if (isTxt(oldNode) && isTxt(newNode)) {//isTxt用来判断是否是文本,为了简便这边并没有声明if (newNode !== oldNode) currentPatch.push({ type: "text", content: newNode })//如果发现文本不同,currentPatch会记录一个差异 }else if(oldNode.tagName === newNode.tagName && oldNode.key === newNode.key){//如果发现两个节点一样 则去判断节点是属性是否一样,并记录下来let attrsPatches = diffAttrs(oldNode, newNode)if (attrsPatches) {//有属性差异则把差异记录下来currentPatch.push({ type: "attrs", "attrs": attrsPatches })}// 递归遍历子节点,并对子节点进行diff比较diffChildren(oldNode.children, newNode.children, index, patches)}else{//最后一种情况是,两个节点完全不一样,这样只需要把旧节点之间替换就行//把当前差异记录下来currentPatch.push({ type: "replace", node: newNode})}//如果有差异则记录到当前层去if (currentPatch.length) {if (patches[index]) {patches[index] = patches[index].concat(currentPatch)} else {patches[index] = currentPatch}}
}
//判断两个节点的属性差异
function diffAttrs(oldNode, newNode){let attrsPatches = {};//记录差异let count = 0;//记录差异的条数/**代码略判断两个节点的属性差异的代码就略了,让你们知道这里的代码就是判断两个节点的属性有哪些差异,如果有差异就记录在attrsPatches这个对象中,并把它返回**/if(0 == count){return null;}else {return attrsPatches; }
}
//判断孩子节点
function diffChildren(oldChild, newChild, index, patches){let { changes, list } = listDiff(oldChild, newChild, index, patches);if (changes.length) {//如果有差异则记录到当前层去if (patches[index]) {patches[index] = patches[index].concat(changes);} else {patches[index] = changes;}}// 代码略//遍历当前数组oldChild && oldChild.forEach((item, i) => {// 代码略let node;// 经过判断后node节点是同时存在于oldChild 和 newChild中//则对节点进行递归遍历 相当于 进入下一层 节点,let curIndex;dfs(item, node, curIndex, patches);// 代码略})}//判断oldNodeList, newNodeList 节点的位置差,主要是为了判断哪些节点被移动、删除、新增。
function listDiff(oldNodeList, newNodeList, index){let changes = [];//记录 oldNodeList, newNodeList节点的位置差异,是被移动、删除、新增let list = [];//记录 oldNodeList,newNodeList 同时存在的节点/**具体判断逻辑的代码就略了**/return {changes,list};
}

如果大家对函数之间的调用还不明白的话可以看下面的图

最后

Virtual Dom 算法的实现也就是以下三步

  • 通过 JS 来模拟生成 Virtual Dom Tree
  • 判断两个 Tree 的差异
  • 渲染差异

上面省略了很多代码,主要是为了让大家快速了解Dom diff 的基本原理和流程,如果想更深入的了解,可以在网上查阅相关资料。

做diff_Virtual Dom amp;amp; Diff原理,极简版相关推荐

  1. 7句话让Codex给我做了个小游戏,还是极简版塞尔达,一玩简直停不下来

    点击上方"视学算法",选择加"星标"或"置顶" 重磅干货,第一时间送达 梦晨 萧箫 发自 凹非寺 量子位 | 公众号 QbitAI 什么,7 ...

  2. 很多小伙伴不太了解ORM框架的底层原理,这不,冰河带你10分钟手撸一个极简版ORM框架(赶快收藏吧)

    大家好,我是冰河~~ 最近很多小伙伴对ORM框架的实现很感兴趣,不少读者在冰河的微信上问:冰河,你知道ORM框架是如何实现的吗?比如像MyBatis和Hibernate这种ORM框架,它们是如何实现的 ...

  3. python3web库_基于 Python3 写的极简版 webserver

    基于 Python3 写的极简版 webserver.用于学习 HTTP协议,及 WEB服务器 工作原理.笔者对 WEB服务器 的工作原理理解的比较粗浅,仅是基于个人的理解来写的,存在很多不足和漏洞, ...

  4. 【Liunx】进程的程序替换——自定义编写极简版shell

    目录 进程程序替换[1~5] 1.程序替换的接口(加载器) 2.什么是程序替换? 3.进程替换的原理 4.引入多进程 5.系列程序替换接口的详细解析(重点!) 自定义编写一个极简版shell[6~8] ...

  5. 10分钟手撸极简版ORM框架!

    最近很多小伙伴对ORM框架的实现很感兴趣,不少读者在冰河的微信上问:冰河,你知道ORM框架是如何实现的吗?比如像MyBatis和Hibernte这种ORM框架,它们是如何实现的呢? 为了能够让小伙伴们 ...

  6. 《重大人生启示录》极简版

    <重大人生启示录>极简版 献给所有活着和将要死去的人们,献给所有历经悲伤的人们 本文摘录了启示录最重要的内容,不必非要看全本,不必购买书 序言 这是极为特殊的历史转折期,物质文明发展到这一 ...

  7. 【极简版GH60】【GH60剖析】【二】原理图的分析

    说完了配件的选择,下面分析下原理图,因为我要做的是极简的GH60,所以PCB上的原件有很多都不需要焊上去,我们来分析下GH60revQE原理图. 首先可以看到大概有61个按键,然后每个按键由一个轴,一 ...

  8. Atlas 200 DK开发者套件环境部署(1.0.9.alpha)极简版

    Atlas 200 DK开发者套件环境部署(1.0.9.alpha)极简版 前言 Atlas 200 DK开发者套件介绍 环境部署介绍 资源要求 开发环境部署 安装Docker 获取镜像(两种方法任选 ...

  9. 美团推出极简版 为用户提供“米面粮油”等生活用品采购服务

    近日,有用户反馈,安卓应用商店显示,美团更新推出了极简版,对主应用的功能进行了删减,保留了美团主应用中涉及生活用品采购的相关业务.用户在打开极简版后,首页会呈现出采购蔬果.米面水油等生活用品的购买入口 ...

最新文章

  1. MVC ---- DBHelper.ttinclude
  2. 【Android 高性能音频】OboeTest 音频性能测试应用 ( 应用简介 | 测试内容 | 输出测试 | Oboe 缓冲区 与 工作负载修改 | 测试案例 )
  3. Exchange 2010 运维技巧一
  4. SharePoint关于publish page, WiKi page, Web part page区别
  5. IOS 获取农历方法(转)
  6. Linux救援模式实战
  7. 20110123 - 调试时IDE的匪夷所思行为
  8. C++顺序表(模板总结)
  9. html自动跳转到锚点,html中的锚点
  10. 1319 移棋子游戏(sg函数模板)
  11. excel冻结窗口_excel中使用快捷方式锁定单元格,冻结某些单元格、公式或单元格...
  12. EMC传导骚扰的共模电流与差模电流
  13. w3c怎么检测html5,HTML5教程:html标签属性通过w3c验证
  14. 牛顿法 泰勒二次展开式
  15. VS创建 Win32 应用程序(实现窗口的创建)
  16. linux中c语言kbhit函数用法,检测按键(Linux中kbhit()函数的实现)
  17. Vue实现动态复选框的全选 全不选 获取选中值
  18. [Android]Android studio编译程序时报错: error: use of undeclared identifier ’free free(t);
  19. 微信分账:分账接收方列表格式错误
  20. 可见性,原子性,有序性的源头(一)

热门文章

  1. java人脸识别_自从加了PC人脸识别登录功能,网站立马显得高大上
  2. c语言编程 输入螺旋数组,C语言 经典题目螺旋矩阵 实例详解
  3. 计算机应用能力大纲,计算机应用能力水平考试大纲.doc
  4. mysql 魔术设置_详解php魔术方法(Magic methods)的使用方法
  5. PHP被忽视的编码规范
  6. linux tomcat java heap space_Linux下tomcat JVM内存设置
  7. oracle 求时间差年,Oracle计算时间差常用函数
  8. MySQL笔记创建表结构_mysql笔记
  9. java ArrayList排序
  10. php的功能和特点,php有什么特点