patch(oldVnode,newVnode)

  • 俗称打补丁,把新节点中变化的内容渲染到真实DOM,最后返回新节点作为下一次处理的旧节点

  • 对比新旧VNode是否相同节点(节点的key和sel相同)

  • 如果不是相同节点,删除之前的内容,重新渲染

  • 如果是相同节点,再判断新的VNode是否有text,如果有并且和oldVnode的test不同,直接更新文本内容;

  • 如果新的VNode有children,判断子节点是否有变化,判断子节点的过程使用就是diff算法;

  • diff过程只进行同层级比较;

import { DOMAPI, htmlDomApi } from "./htmldomapi";
import * as is from "./is";
import { Module } from "./modules/module";
import { vnode, VNode } from "./vnode";
// 导入依赖的模块// 定义类型
type NonUndefined<T> = T extends undefined ? never : T;function isUndef(s: any): boolean {return s === undefined;
}
function isDef<A>(s: A): s is NonUndefined<A> {return s !== undefined;
}type VNodeQueue = VNode[];const emptyNode = vnode("", {}, [], undefined, undefined);function sameVnode(vnode1: VNode, vnode2: VNode): boolean {const isSameKey = vnode1.key === vnode2.key;const isSameIs = vnode1.data?.is === vnode2.data?.is;const isSameSel = vnode1.sel === vnode2.sel;return isSameSel && isSameKey && isSameIs;
}function isVnode(vnode: any): vnode is VNode {return vnode.sel !== undefined;
}type KeyToIndexMap = { [key: string]: number };type ArraysOf<T> = {[K in keyof T]: Array<T[K]>;
};type ModuleHooks = ArraysOf<Required<Module>>;function createKeyToOldIdx(children: VNode[],beginIdx: number,endIdx: number
): KeyToIndexMap {const map: KeyToIndexMap = {};for (let i = beginIdx; i <= endIdx; ++i) {const key = children[i]?.key;if (key !== undefined) {map[key as string] = i;}}return map;
}// 定义了钩子函数
const hooks: Array<keyof Module> = ["create","update","remove","destroy","pre","post",
];export function init(modules: Array<Partial<Module>>, domApi?: DOMAPI) {let i: number;let j: number;const cbs: ModuleHooks = {create: [],update: [],remove: [],destroy: [],pre: [],post: [],};// 初始化转化虚拟节点的apiconst api: DOMAPI = domApi !== undefined ? domApi : htmlDomApi;// 把传入的所有模块的钩子函数,统一存储到cbs对象中;// 最终构建的cbs 对象的形式cbs = {creat:[fn1,fn2],update:[],...}for (i = 0; i < hooks.length; ++i) {// cbs.create = [],cbs.update=[].....cbs[hooks[i]] = [];for (j = 0; j < modules.length; ++j) {// modules传入的模块数组// 获取模块中的hook函数// hook = modules[0][create].....const hook = modules[j][hooks[i]];if (hook !== undefined) {// 把获取到的hook函数放入到cbs 对应的钩子函数数组中(cbs[hooks[i]] as any[]).push(hook);}}}function emptyNodeAt(elm: Element) {const id = elm.id ? "#" + elm.id : "";// elm.className doesn't return a string when elm is an SVG element inside a shadowRoot.// https://stackoverflow.com/questions/29454340/detecting-classname-of-svganimatedstringconst classes = elm.getAttribute("class");const c = classes ? "." + classes.split(" ").join(".") : "";return vnode(api.tagName(elm).toLowerCase() + id + c,{},[],undefined,elm);}function createRmCb(childElm: Node, listeners: number) {return function rmCb() {if (--listeners === 0) {const parent = api.parentNode(childElm) as Node;api.removeChild(parent, childElm);}};}function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {let i: any;let data = vnode.data;if (data !== undefined) {const init = data.hook?.init;if (isDef(init)) {init(vnode);data = vnode.data;}}const children = vnode.children;const sel = vnode.sel;if (sel === "!") {if (isUndef(vnode.text)) {vnode.text = "";}vnode.elm = api.createComment(vnode.text!);} else if (sel !== undefined) {// Parse selectorconst hashIdx = sel.indexOf("#");const dotIdx = sel.indexOf(".", hashIdx);const hash = hashIdx > 0 ? hashIdx : sel.length;const dot = dotIdx > 0 ? dotIdx : sel.length;const tag =hashIdx !== -1 || dotIdx !== -1? sel.slice(0, Math.min(hash, dot)): sel;const elm = (vnode.elm =isDef(data) && isDef((i = data.ns))? api.createElementNS(i, tag, data): api.createElement(tag, data));if (hash < dot) elm.setAttribute("id", sel.slice(hash + 1, dot));if (dotIdx > 0)elm.setAttribute("class", sel.slice(dot + 1).replace(/\./g, " "));for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode, vnode);if (is.array(children)) {for (i = 0; i < children.length; ++i) {const ch = children[i];if (ch != null) {api.appendChild(elm, createElm(ch as VNode, insertedVnodeQueue));}}} else if (is.primitive(vnode.text)) {api.appendChild(elm, api.createTextNode(vnode.text));}const hook = vnode.data!.hook;if (isDef(hook)) {hook.create?.(emptyNode, vnode);if (hook.insert) {insertedVnodeQueue.push(vnode);}}} else {vnode.elm = api.createTextNode(vnode.text!);}return vnode.elm;}function addVnodes(parentElm: Node,before: Node | null,vnodes: VNode[],startIdx: number,endIdx: number,insertedVnodeQueue: VNodeQueue) {for (; startIdx <= endIdx; ++startIdx) {const ch = vnodes[startIdx];if (ch != null) {api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);}}}function invokeDestroyHook(vnode: VNode) {const data = vnode.data;if (data !== undefined) {data?.hook?.destroy?.(vnode);for (let i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode);if (vnode.children !== undefined) {for (let j = 0; j < vnode.children.length; ++j) {const child = vnode.children[j];if (child != null && typeof child !== "string") {invokeDestroyHook(child);}}}}}function removeVnodes(parentElm: Node,vnodes: VNode[],startIdx: number,endIdx: number): void {for (; startIdx <= endIdx; ++startIdx) {let listeners: number;let rm: () => void;const ch = vnodes[startIdx];if (ch != null) {if (isDef(ch.sel)) {invokeDestroyHook(ch);listeners = cbs.remove.length + 1;rm = createRmCb(ch.elm!, listeners);for (let i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm);const removeHook = ch?.data?.hook?.remove;if (isDef(removeHook)) {removeHook(ch, rm);} else {rm();}} else {// Text nodeapi.removeChild(parentElm, ch.elm!);}}}}function updateChildren(parentElm: Node,oldCh: VNode[],newCh: VNode[],insertedVnodeQueue: VNodeQueue) {let oldStartIdx = 0;let newStartIdx = 0;let oldEndIdx = oldCh.length - 1;let oldStartVnode = oldCh[0];let oldEndVnode = oldCh[oldEndIdx];let newEndIdx = newCh.length - 1;let newStartVnode = newCh[0];let newEndVnode = newCh[newEndIdx];let oldKeyToIdx: KeyToIndexMap | undefined;let idxInOld: number;let elmToMove: VNode;let before: any;while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {if (oldStartVnode == null) {oldStartVnode = oldCh[++oldStartIdx]; // Vnode might have been moved left} else if (oldEndVnode == null) {oldEndVnode = oldCh[--oldEndIdx];} else if (newStartVnode == null) {newStartVnode = newCh[++newStartIdx];} else if (newEndVnode == null) {newEndVnode = newCh[--newEndIdx];} else if (sameVnode(oldStartVnode, newStartVnode)) {patchVnode(oldStartVnode, newStartVnode, insertedVnodeQueue);oldStartVnode = oldCh[++oldStartIdx];newStartVnode = newCh[++newStartIdx];} else if (sameVnode(oldEndVnode, newEndVnode)) {patchVnode(oldEndVnode, newEndVnode, insertedVnodeQueue);oldEndVnode = oldCh[--oldEndIdx];newEndVnode = newCh[--newEndIdx];} else if (sameVnode(oldStartVnode, newEndVnode)) {// Vnode moved rightpatchVnode(oldStartVnode, newEndVnode, insertedVnodeQueue);api.insertBefore(parentElm,oldStartVnode.elm!,api.nextSibling(oldEndVnode.elm!));oldStartVnode = oldCh[++oldStartIdx];newEndVnode = newCh[--newEndIdx];} else if (sameVnode(oldEndVnode, newStartVnode)) {// Vnode moved leftpatchVnode(oldEndVnode, newStartVnode, insertedVnodeQueue);api.insertBefore(parentElm, oldEndVnode.elm!, oldStartVnode.elm!);oldEndVnode = oldCh[--oldEndIdx];newStartVnode = newCh[++newStartIdx];} else {if (oldKeyToIdx === undefined) {oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx);}idxInOld = oldKeyToIdx[newStartVnode.key as string];if (isUndef(idxInOld)) {// New elementapi.insertBefore(parentElm,createElm(newStartVnode, insertedVnodeQueue),oldStartVnode.elm!);} else {elmToMove = oldCh[idxInOld];if (elmToMove.sel !== newStartVnode.sel) {api.insertBefore(parentElm,createElm(newStartVnode, insertedVnodeQueue),oldStartVnode.elm!);} else {patchVnode(elmToMove, newStartVnode, insertedVnodeQueue);oldCh[idxInOld] = undefined as any;api.insertBefore(parentElm, elmToMove.elm!, oldStartVnode.elm!);}}newStartVnode = newCh[++newStartIdx];}}if (oldStartIdx <= oldEndIdx || newStartIdx <= newEndIdx) {if (oldStartIdx > oldEndIdx) {before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;addVnodes(parentElm,before,newCh,newStartIdx,newEndIdx,insertedVnodeQueue);} else {removeVnodes(parentElm, oldCh, oldStartIdx, oldEndIdx);}}}function patchVnode(oldVnode: VNode,vnode: VNode,insertedVnodeQueue: VNodeQueue) {const hook = vnode.data?.hook;hook?.prepatch?.(oldVnode, vnode);const elm = (vnode.elm = oldVnode.elm)!;const oldCh = oldVnode.children as VNode[];const ch = vnode.children as VNode[];if (oldVnode === vnode) return;if (vnode.data !== undefined) {for (let i = 0; i < cbs.update.length; ++i)cbs.update[i](oldVnode, vnode);vnode.data.hook?.update?.(oldVnode, vnode);}if (isUndef(vnode.text)) {if (isDef(oldCh) && isDef(ch)) {if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue);} else if (isDef(ch)) {if (isDef(oldVnode.text)) api.setTextContent(elm, "");addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue);} else if (isDef(oldCh)) {removeVnodes(elm, oldCh, 0, oldCh.length - 1);} else if (isDef(oldVnode.text)) {api.setTextContent(elm, "");}} else if (oldVnode.text !== vnode.text) {if (isDef(oldCh)) {removeVnodes(elm, oldCh, 0, oldCh.length - 1);}api.setTextContent(elm, vnode.text!);}hook?.postpatch?.(oldVnode, vnode);}// init内部返回patch函数,把vnode渲染成真实的dom,并返回vnode// return 一个函数 ,这个函数是一个高阶函数return function patch(oldVnode: VNode | Element, vnode: VNode): VNode {let i: number, elm: Node, parent: Node;// 保存新插入节点的队列,为了触发钩子函数const insertedVnodeQueue: VNodeQueue = [];// 执行模块的pre钩子函数for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i]();// 如果oldVnode 不是VNode,创建VNode并设置elmif (!isVnode(oldVnode)) {// 把DOM元素转化成空的VNodeoldVnode = emptyNodeAt(oldVnode);}// 如果新旧节点是相同节点(key 和 sel相同)if (sameVnode(oldVnode, vnode)) {// 找节点的差异并更新DOMpatchVnode(oldVnode, vnode, insertedVnodeQueue);} else {// 如果新旧节点不同,vnode创建对应的DOM// 获取当前的DOM元素elm = oldVnode.elm!; // !表示必定存在parent = api.parentNode(elm) as Node; // 找到父级元素// 创建 vnode对应的DOM元素,并触发init/create钩子函数;createElm(vnode, insertedVnodeQueue);if (parent !== null) {// 如果父节点不为空,把vnode对应的DOM插入到文档中api.insertBefore(parent, vnode.elm!, api.nextSibling(elm));// 移出老节点removeVnodes(parent, [oldVnode], 0, 0);}}// 执行用户设置的insert钩子函数for (i = 0; i < insertedVnodeQueue.length; ++i) {insertedVnodeQueue[i].data!.hook!.insert!(insertedVnodeQueue[i]);}// 执行模块的post钩子函数for (i = 0; i < cbs.post.length; ++i) cbs.post[i]();// 返回vnodereturn vnode;};
}

Snabbdom(虚拟dom-5-patch函数)相关推荐

  1. 十二、虚拟 DOM 和 render() 函数(1)

    本章概要 虚拟DOM render()函数 Vue.js 之所以执行性能高,一个很重要的原因就是它的虚拟 DOM 机制. 12.1 虚拟 DOM 浏览器在解析 HTML 文档时,会将文档中的元素.注释 ...

  2. Vue深入学习—虚拟DOM和Diff算法

    1.snabbdom 是什么? snabbdom是"速度"的意思,源码只有200行,使用TS写的,让东西变得模块化 2.snabbdom 的 h 函数如何工作? h函数用于产生虚拟 ...

  3. 深入浅出虚拟 DOM 和 Diff 算法,及 Vue2 与 Vue3 中的区别

    因为 Diff 算法,计算的就是虚拟 DOM 的差异,所以先铺垫一点点虚拟 DOM,了解一下其结构,再来一层层揭开 Diff 算法的面纱,深入浅出,助你彻底弄懂 Diff 算法原理 认识虚拟 DOM ...

  4. vue:虚拟dom的实现

    Vitual DOM是一种虚拟dom技术,本质上是基于javascript实现的,相对于dom对象,javascript对象更简单,处理速度更快,dom树的结构,属性信息都可以很容易的用javascr ...

  5. React简介、虚拟DOM、Diff算法、创建React项目、JSX语法、组件、组件声明方式、组件传值props和state、组件的生命周期

    React简介: 前面只是简单介绍移动APP开发,后面还会继续深入介绍移动app开发:其中想要用ReactNative开发出更出色的应用,那么就得学好React,下面将介绍React: React 是 ...

  6. Github每日精选(第61期):虚拟 DOM 走向未来 million

    million million 使创建用户界面像React一样简单,但为最终用户提供更快的性能和更小的包大小.通过使用编译器预先计算用户界面,Million 减少了传统 Virtual DOM 的开销 ...

  7. React框架简介(JSX语法、组件、虚拟DOM渲染)

    目录 React框架 为什么要学习React React特点 React核心 JSX语法 语法详解 React开发过程 实际DOM 虚拟DOM React组件 函数组件 类组件 虚拟DOM渲染过程 R ...

  8. Vue 这更新是要抛弃虚拟 DOM ?

    最近 Vue 在美国举办了 Vue Conf 2022,不过可惜的是在国内并未掀起任何的波澜,于是我来试试能不能一石激起千层浪,因为尤雨溪在 Vue Conf 上说他们正在探索一种新的编译策略. 流行 ...

  9. 解密虚拟 DOM——snabbdom 核心源码解读

    本文源码地址:https://github.com/zhongdeming428/snabbdom 对很多人而言,虚拟 DOM 都是一个很高大上而且远不可及的专有名词,以前我也这么认为,后来在学习 V ...

  10. 【Virtual DOM】虚拟 DOM 和 Snabbdom 库

    前言 笔记来源:拉勾教育 大前端高薪训练营 阅读建议:建议通过左侧导航栏进行阅读 Virtual DOM 基本介绍 什么是 Virtual DOM Virtual DOM(虚拟 DOM),是由普通的的 ...

最新文章

  1. Linux内存技术分析(下)
  2. hash 值重复_程序员:判断对象是否重复,不重写equals和hashcode不行吗?
  3. 《转》atomic assign retain
  4. Translating Embedding for Modeling Multi-relational Data
  5. [codevs 1227] 方格取数2
  6. 【资讯】人工智能专业职称来了,快来看看你够不够申请资格!
  7. 对偶图 【BZOJ】1001: [BeiJing2006]狼抓兔子(对偶图+最短路)
  8. 如何快速实现 Wordpress 博客域名更换?
  9. 大数据_Flink_Java版_数据处理_时间语义(1)_时间语义概念---Flink工作笔记0049
  10. eclipse字体颜色设置
  11. 初二生态系统思维导图_有关初中生态系统的思维导图
  12. VS Code 创建HTML页面教程
  13. python图片水印_用python来给图片加水印
  14. 泛函分析极简笔记(2)——Mahalanobis distance
  15. 【K8S etcd篇】部署etcd 3.4.14 集群
  16. SQL语句插入日期类型
  17. 照葫芦画瓢之python爬虫系列----(2)初次爬取简单的动态网页数据(网易、QQ音乐排行榜)
  18. Github pages个人域名添加SSL
  19. 百度网盘下载失败【1252017】误报违规
  20. Cakephp分组查询

热门文章

  1. EifficientDet论文笔记
  2. IOS之block,一点小心得
  3. 搭建自己的博客(二十六):优化点赞功能,并添加模态登录框
  4. 【U3D】掉落物设计
  5. agile/scrum 如果一切都从解放前开始
  6. 带进度条的文件复制。
  7. Sql中对大数据量的判断
  8. Java中的强软弱虚引用《对Java的分析总结三》
  9. 使用 Pandas 的 to_excel() 方法来将多个 csv 文件合并到一个 xlsx 的不同 sheets 内
  10. 机器学习之--数据构造,函数图显示