首先先来聊一下,什么是虚拟dom?
为什么要使用虚拟dom?

其实答案很简单,虚拟dom就是位于js层和html层之间的一个层,使用js模拟出一个dom树,然后通过diff算法,来侦测到dom发生更改的准确信息,然后再通知html层进行更改.

至于为什么要使用虚拟dom其实原因也很实在,无非就是为了节约性能.

同学们之前可能都或多或少的了解过,我们的应用运行时损耗性能较大的就是频繁的dom操作,而dom操作会触发重绘或者回流

而虚拟dom的出现则解决了这个问题,由于js运行速度较快,我们可以通过使用js模拟dom树,形成一个虚拟dom树,再进行更改的时候速度就会快很多,只需要把更改的结果反馈给html层即可,大大减少了重绘与回流的触发.

闲话扯完了,开始代码部分


首先我们需要编写一个类,实现使用js模拟dom树

class 


(高能预警) 这里是本文的核心内容!!!

然后是使用diff算法检测差异,然后生成一个包含差异信息的对象

首先我们先了解一下,我们是按照什么样的顺序获取差异的?

而我们又是如何标记新树和老树之间的差异呢?

两个节点之间的差异有总结起来有下面4种

0 代表 直接替换原有节点
1 代表 调整子节点,包括移动、删除等
2 代表 修改节点属性
3 代表 修改节点文本内容

而我们生成的差异对象应该是这样的

{"1": [{"type": 0,"node": {"tagName": "h3","props": {"style": "color: green"},"children": ["I am H1"],"count": 1}}]
}

实现的代码则应该是这样的

import listDiff from 'list-diff2'
// 每个节点有四种变动
export const REPLACE = 0 // 替换原有节点
export const REORDER = 1 // 调整子节点,包括移动、删除等
export const PROPS = 2 // 修改节点属性
export const TEXT = 3 // 修改节点文本内容export function diff (oldTree, newTree) {// 节点的遍历顺序let index = 0// 在遍历过程中记录节点的差异let patches = {}// 深度优先遍历两棵树deepTraversal(oldTree, newTree, index, patches)// 得到的差异对象返回出去return patches
}function deepTraversal(oldNode, newNode, index, patches) {let currentPatch = []if (newNode === null) { // 如果新节点没有的话直接不用比较了return}if (typeof oldNode === 'string' && typeof newNode === 'string') {// 比较文本节点if (oldNode !== newNode) {currentPatch.push({type: TEXT,content: newNode})}} else if (oldNode.tagName === newNode.tagName && oldNode.key === newNode.key) {// 节点类型相同// 比较节点的属性是否相同let propasPatches = diffProps(oldNode, newNode)if (propasPatches) {currentPatch.push({type: PROPS,props: propsPatches})}// 递归比较子节点是否相同diffChildren(oldNode.children, newNode.children, index, patches, currentPatch)} else {// 节点不一样,直接替换currentPatch.push({ type: REPLACE, node: newNode })}if (currentPatch.length) {// 那个index节点的差异记录下来patches[index] = currentPatch}}// 子数的diff
function diffChildren (oldChildren, newChildren, index, patches, currentPatch) {var diffs = listDiff(oldChildren, newChildren)newChildren = diffs.children// 如果调整子节点,包括移动、删除等的话if (diffs.moves.length) {var reorderPatch = {type: REORDER,moves: diffs.moves}currentPatch.push(reorderPatch)}var leftNode = nullvar currentNodeIndex = indexoldChildren.forEach((child, i) => {var newChild = newChildren[i]// index相加currentNodeIndex = (leftNode && leftNode.count) ? currentNodeIndex + leftNode.count + 1 : currentNodeIndex + 1// 深度遍历,从左树开始deepTraversal(child, newChild, currentNodeIndex, patches)// 从左树开始leftNode = child})
}// 记录属性的差异
function diffProps (oldNode, newNode) {let count = 0 // 声明一个有没没有属性变更的标志const oldProps = oldNode.propsconst newProps = newNode.propsconst propsPatches = {}// 找出不同的属性for (let [key, val] of Object.entries(oldProps)) {// 新的不等于旧的if (newProps[key] !== val) {count++propsPatches[key] = newProps[key]}}// 找出新增的属性for (let [key, val] of Object.entries(newProps)) {if (!oldProps.hasOwnProperty(key)) {count++propsPatches[key] = val}}// 没有新增 也没有不同的属性 直接返回nullif (count === 0) {return null}return propsPatches
}


最后,就是我们将差异对象解析并修改dom树.

import {REPLACE, REORDER, PROPS, TEXT} from './diff';export function patch (node, patches) {// 也是从0开始const step = {index: 0}// 深度遍历deepTraversal(node, step, patches)
}// 深度优先遍历dom结构
function deepTraversal(node, step, patches) {// 拿到当前差异对象const currentPatches = patches[step.index]const len = node.childNodes ? node.childNodes.length : 0for (let i = 0; i < len; i++) {const child = node.childNodes[i]step.index++deepTraversal(child, step, patches)}//如果当前节点存在差异if (currentPatches) {// 把差异对象应用到当前节点上applyPatches(node, currentPatches)}
}// 把差异对象应用到当前节点上
function applyPatches(node, currentPatches) {currentPatches.forEach(currentPatch => {switch (currentPatch.type) {// 0: 替换原有节点case REPLACE:var newNode = (typeof currentPatch.node === 'string') ?  document.createTextNode(currentPatch.node) : currentPatch.node.render()node.parentNode.replaceChild(newNode, node)break// 1: 调整子节点,包括移动、删除等case REORDER: moveChildren(node, currentPatch.moves)break// 2: 修改节点属性case PROPS:for (let [key, val] of Object.entries(currentPatch.props)) {if (val === undefined) {node.removeAttribute(key)} else {node.setAttribute(key, value);}}break;// 3:修改节点文本内容case TEXT:if (node.textContent) {node.textContent = currentPatch.content} else {node.nodeValue = currentPatch.content}break;default: throw new Error('Unknow patch type ' + currentPatch.type);}})
}// 调整子节点,包括移动、删除等
function moveChildren (node, moves) {let staticNodelist = Array.from(node.childNodes)const maps = {}staticNodelist.forEach(node => {if (node.nodeType === 1) {const key = node.getAttribute('key')if (key) {maps[key] = node}}})moves.forEach(move => {const index = move.indexif (move.type === 0) { // 变动类型为删除的节点if (staticNodeList[index] === node.childNodes[index]) {node.removeChild(node.childNodes[index]);}staticNodeList.splice(index, 1);} else {let insertNode = maps[move.item.key] ? maps[move.item.key] : (typeof move.item === 'object') ? move.item.render() : document.createTextNode(move.item)staticNodelist.splice(index, 0, insertNode);node.insertBefore(insertNode, node.childNodes[index] || null)}})
}

diff算法_[VUE学习]徒手撸一个虚拟dom+diff算法相关推荐

  1. cart算法_决策树学习笔记(三):CART算法,决策树总结

    点击上方"Python数据科学",选择"星标公众号" 关键时刻,第一时间送达! 作者:xiaoyu 介绍:一个半路转行的数据挖掘工程师 推荐导读:本篇为树模型系 ...

  2. java opencv磨皮算法_深度学习AI美颜系列 - AI美颜磨皮算法[转]

    原文:OpenCV学堂 原创作者:胡耀武 转载,以学习,记录,备忘. 这里先放对比结果图,原图来自网络: 1. 算法的流程 [1] - 皮肤分割算法 [2] - 人脸关键点识别算法 [3] - 基于皮 ...

  3. spring boot 自动跳转登录页面_徒手撸一个扫码登录示例工程

    徒手撸一个扫码登录示例工程 不知道是不是微信的原因,现在出现扫码登录的场景越来越多了,作为一个有追求.有理想新四好码农,当然得紧跟时代的潮流,得徒手撸一个以儆效尤 本篇示例工程,主要用到以下技术栈 q ...

  4. 深入浅出React(四):虚拟DOM Diff算法解析

    React中最神奇的部分莫过于虚拟DOM,以及其高效的Diff算法.这让我们可以无需担心性能问题而"毫无顾忌"的随时"刷新"整个页面,由虚拟DOM来确保只对界面 ...

  5. React虚拟DOM Diff算法解析

    React中最神奇的部分莫过于虚拟DOM,以及其高效的Diff算法.这让我们可以无需担心性能问题而"毫无顾忌"的随时"刷新"整个页面,由虚拟DOM来确保只对界面 ...

  6. 虚拟DOM Diff算法解析

    React中最神奇的部分莫过于虚拟DOM,以及其高效的Diff算法.这让我们可以无需担心性能问题而"毫无顾忌"的随时"刷新"整个页面,由虚拟DOM来确保只对界面 ...

  7. [vue] 如何实现一个虚拟DOM?说说你的思路

    [vue] 如何实现一个虚拟DOM?说说你的思路 虚拟DOM本身是一个JavaScript对象模拟真实DOM ,用对象的属性去描述一个DOM节点,最终也只是一个真实DOM的映射 个人简介 我是歌谣,欢 ...

  8. 构建一个虚拟DOM并转换为真实DOM

    关于真实DOM与虚拟DOM 1.在学习虚拟DOM之前,让我们先来了解一下真实的DOM结构,这里不得不提的是关于浏览器渲染方面的知识. 当浏览器拿到一个HTML文件,首先会根据HTML文件构建出一个DO ...

  9. 面试官:什么是虚拟DOM?如何实现一个虚拟DOM?

    故心故心故心故心小故冲啊 文章目录 一.什么是虚拟DOM 二.为什么需要虚拟DOM 三.如何实现虚拟DOM 小结 参考文献 一.什么是虚拟DOM 虚拟 DOM (Virtual DOM )这个概念相信 ...

最新文章

  1. memcahce 介绍以及安装以及扩展的安装
  2. 大数运算(1)——大数储存
  3. HDFS EditsLog和FsImage日志机制
  4. 安卓学习 之 UI控件(三)
  5. Flask/Django/Tornado语法对比(持续更新中)
  6. ZOJ1221 Risk 图形的遍历
  7. csv是python内置模块吗_Python--CSV模块 - 一只小小的寄居蟹 - 博客园
  8. 慕课网 饿了么 vue2.0 项目
  9. 区块链java语言,基于Java语言构建区块链(一)—— 基本原型
  10. win7 docker centos安装mysql_CentOS 7 使用docker安装mysql
  11. java 邮件发送 demo_Java 邮件发送Demo
  12. Windows自带利器:Rundll.exe高级应用
  13. php数组中去掉空格,php数组如何去除空格
  14. urule客户端和服务器配置
  15. 基础篇:6.6)形位公差-基准 Datum
  16. 基于STM32H7的ADS1256驱动案例,8通道,24bit ADC,带可编程增益(2021-09-20)
  17. 【概率论与数理统计】1.5 独立性
  18. JS逆向之国家企业信用信息公示系统Cookie传递
  19. 【NIPS 2017】PointNet++:度量空间中点集的深层次特征学习
  20. pop3 postfix 命令_POP3/SMTP/IMAP4 常用命令

热门文章

  1. baq在聊天中啥意思_职场中的“老实人”如何实现逆袭,得到领导的重用?
  2. java中什么是字节流和字符流_java中字节流与字符流的区别是什么
  3. python合并excel工作簿_使用Python将多个excel的多个sheet页合并到一个excel
  4. Python爬取抖音用户相关数据(目前最方便的方法)
  5. scp命令不会复制隐藏文件
  6. Software-Defined Networking (SDN) Definition-软件定义网络
  7. fullPage.js给网站加上全屏幻灯片的展示效果
  8. 网站服务器空间选择,网站服务器空间选择
  9. python 判断线程是否执行完毕_判断线程池中的线程是否全部执行完毕
  10. nodejs参数的接收(post和get)