vue在官方文档中提到与react的渲染性能对比中,因为其使用了snabbdom而有更优异的性能。

JavaScript 开销直接与求算必要 DOM 操作的机制相关。尽管 Vue 和 React 都使用了 Virtual Dom 实现这一点,但 Vue 的 Virtual Dom 实现(复刻自 snabbdom)是更加轻量化的,因此也就比 React 的实现更高效。

看到火到不行的国产前端框架vue也在用别人的 Virtual Dom开源方案,是不是很好奇snabbdom有何强大之处呢?不过正式解密snabbdom之前,先简单介绍下Virtual Dom。

什么是Virtual Dom

Virtual Dom可以看做一棵模拟了DOM树的JavaScript树,其主要是通过vnode,实现一个无状态的组件,当组件状态发生更新时,然后触发Virtual Dom数据的变化,然后通过Virtual Dom和真实DOM的比对,再对真实DOM更新。可以简单认为Virtual Dom是真实DOM的缓存。

为什么用Virtual Dom

我们知道,当我们希望实现一个具有复杂状态的界面时,如果我们在每个可能发生变化的组件上都绑定事件,绑定字段数据,那么很快由于状态太多,我们需要维护的事件和字段将会越来越多,代码也会越来越复杂,于是,我们想我们可不可以将视图和状态分开来,只要视图发生变化,对应状态也发生变化,然后状态变化,我们再重绘整个视图就好了。

这样的想法虽好,但是代价太高了,于是我们又想,能不能只更新状态发生变化的视图?于是Virtual Dom应运而生,状态变化先反馈到Virtual Dom上,Virtual Dom在找到最小更新视图,最后批量更新到真实DOM上,从而达到性能的提升。

除此之外,从移植性上看,Virtual Dom还对真实dom做了一次抽象,这意味着Virtual Dom对应的可以不是浏览器的DOM,而是不同设备的组件,极大的方便了多平台的使用。如果是要实现前后端同构直出方案,使用Virtual Dom的框架实现起来是比较简单的,因为在服务端的Virtual Dom跟浏览器DOM接口并没有绑定关系。

基于 Virtual DOM 的数据更新与UI同步机制:

初始渲染时,首先将数据渲染为 Virtual DOM,然后由 Virtual DOM 生成 DOM。

数据更新时,渲染得到新的 Virtual DOM,与上一次得到的 Virtual DOM 进行 diff,得到所有需要在 DOM 上进行的变更,然后在 patch 过程中应用到 DOM 上实现UI的同步更新。

Virtual DOM 作为数据结构,需要能准确地转换为真实 DOM,并且方便进行对比。

介绍完Virtual DOM,我们应该对snabbdom的功用有个认识了,下面具体解剖下snabbdom这只“小麻雀”。

snabbdom

vnode

DOM 通常被视为一棵树,元素则是这棵树上的节点(node),而 Virtual DOM 的基础,就是 Virtual Node 了。

Snabbdom 的 Virtual Node 则是纯数据对象,通过 vnode 模块来创建,对象属性包括:

  • sel
  • data
  • children
  • text
  • elm
  • key

可以看到 Virtual Node 用于创建真实节点的数据包括:

  • 元素类型
  • 元素属性
  • 元素的子节点

源码:

//VNode函数,用于将输入转化成VNode/**  *  * @param sel 选择器  * @param data 绑定的数据  * @param children 子节点数组  * @param text 当前text节点内容  * @param elm 对真实dom element的引用  * @returns {{sel: *, data: *, children: *, text: *, elm: *, key: undefined}}  */ function vnode(sel, data, children, text, elm) { var key = data === undefined ? undefined : data.key; return { sel: sel, data: data, children: children, text: text, elm: elm, key: key }; }

snabbdom并没有直接暴露vnode对象给我们用,而是使用h包装器,h的主要功能是处理参数:

h(sel,[data],[children],[text]) => vnode

从snabbdom的typescript的源码可以看出,其实就是这几种函数重载:

export function h(sel: string): VNode; export function h(sel: string, data: VNodeData): VNode; export function h(sel: string, text: string): VNode; export function h(sel: string, children: Array<VNode | undefined | null>): VNode; export function h(sel: string, data: VNodeData, text: string): VNode; export function h(sel: string, data: VNodeData, children: Array<VNode | undefined | null>): VNode; 

patch

创建vnode后,接下来就是调用patch方法将Virtual Dom渲染成真实DOM了。patch是snabbdom的init函数返回的。
snabbdom.init传入modules数组,module用来扩展snabbdom创建复杂dom的能力。

不多说了直接上patch的源码:

return function patch(oldVnode, vnode) { var i, elm, parent; //记录被插入的vnode队列,用于批触发insert var insertedVnodeQueue = []; //调用全局pre钩子 for (i = 0; i < cbs.pre.length; ++i) cbs.pre[i](); //如果oldvnode是dom节点,转化为oldvnode if (isUndef(oldVnode.sel)) { oldVnode = emptyNodeAt(oldVnode); } //如果oldvnode与vnode相似,进行更新 if (sameVnode(oldVnode, vnode)) { patchVnode(oldVnode, vnode, insertedVnodeQueue); } else { //否则,将vnode插入,并将oldvnode从其父节点上直接删除 elm = oldVnode.elm; parent = api.parentNode(elm); createElm(vnode, insertedVnodeQueue); if (parent !== null) { api.insertBefore(parent, vnode.elm, api.nextSibling(elm)); removeVnodes(parent, [oldVnode], 0, 0); } } //插入完后,调用被插入的vnode的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](); //返回vnode用作下次patch的oldvnode return vnode; };

先判断新旧虚拟dom是否是相同层级vnode,是才执行patchVnode,否则创建新dom删除旧dom,判断是否相同vnode比较简单:

function sameVnode(vnode1, vnode2) {//判断key值和选择器 return vnode1.key === vnode2.key && vnode1.sel === vnode2.sel; }

patch方法里面实现了snabbdom 作为一个高效virtual dom库的法宝—高效的diff算法,可以用一张图示意:

diff算法的核心是比较只会在同层级进行, 不会跨层级比较。而不是逐层逐层搜索遍历的方式,时间复杂度将会达到 O(n^3)的级别,代价非常高,而只比较同层级的方式时间复杂度可以降低到O(n)。

patchVnode函数的主要作用是以打补丁的方式去更新dom树。

function patchVnode(oldVnode, vnode, insertedVnodeQueue) { var i, hook; //在patch之前,先调用vnode.data的prepatch钩子 if (isDef(i = vnode.data) && isDef(hook = i.hook) && isDef(i = hook.prepatch)) { i(oldVnode, vnode); } var elm = vnode.elm = oldVnode.elm, oldCh = oldVnode.children, ch = vnode.children; //如果oldvnode和vnode的引用相同,说明没发生任何变化直接返回,避免性能浪费 if (oldVnode === vnode) return; //如果oldvnode和vnode不同,说明vnode有更新 //如果vnode和oldvnode不相似则直接用vnode引用的DOM节点去替代oldvnode引用的旧节点 if (!sameVnode(oldVnode, vnode)) { var parentElm = api.parentNode(oldVnode.elm); elm = createElm(vnode, insertedVnodeQueue); api.insertBefore(parentElm, elm, oldVnode.elm); removeVnodes(parentElm, [oldVnode], 0, 0); return; } //如果vnode和oldvnode相似,那么我们要对oldvnode本身进行更新 if (isDef(vnode.data)) { //首先调用全局的update钩子,对vnode.elm本身属性进行更新 for (i = 0; i < cbs.update.length; ++i) cbs.update[i](oldVnode, vnode); //然后调用vnode.data里面的update钩子,再次对vnode.elm更新 i = vnode.data.hook; if (isDef(i) && isDef(i = i.update)) i(oldVnode, vnode); } //如果vnode不是text节点 if (isUndef(vnode.text)) { //如果vnode和oldVnode都有子节点 if (isDef(oldCh) && isDef(ch)) { //当Vnode和oldvnode的子节点不同时,调用updatechilren函数,diff子节点 if (oldCh !== ch) updateChildren(elm, oldCh, ch, insertedVnodeQueue); } //如果vnode有子节点,oldvnode没子节点 else if (isDef(ch)) { //oldvnode是text节点,则将elm的text清除 if (isDef(oldVnode.text)) api.setTextContent(elm, ''); //并添加vnode的children addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); } //如果oldvnode有children,而vnode没children,则移除elm的children else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1); } //如果vnode和oldvnode都没chidlren,且vnode没text,则删除oldvnode的text else if (isDef(oldVnode.

转载于:https://www.cnblogs.com/libin-1/p/6801559.html

vue的Virtual Dom实现- snabbdom解密相关推荐

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

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

  2. vue源码分析系列三:render的执行过程和Virtual DOM的产生

    render 手写 render 函数,仔细观察下面这段代码,试想一下这里的 createElement 参数是什么 . new Vue({el: '#application',render(crea ...

  3. Vue进阶之Virtual DOM(虚拟DOM) 实现原理

    Vue进阶之Virtual DOM(虚拟DOM) 实现原理 Virtual DOM(虚拟 DOM),是由普通的 JS 对象来描述 DOM 对象,因为不是真实的 DOM 对象,所以叫 Virtual D ...

  4. [Vue源码分析] Virtual DOM

    最近小组有个关于vue virtual dom的分享会,提前准备一下- 读前须知: 本文章涉及源码版本为Vue 2.5.2,文中涉及到源码部分,解释直接写在源码中(中文部分为本人添加),截图尽量放完整 ...

  5. Vue源码分析系列四:Virtual DOM

    前言 当我们操作Dom其实是一件非常耗性能的事,每个元素都涵盖了许多的属性,因为浏览器的标准就把 DOM 设计的非常复杂.而Virtual Dom就是用一个原生的JS对象去描述一个DOM节点,即VNo ...

  6. vue xunidom_vue的虚拟dom(Virtual DOM )

    模板转换成视图的过程 在底层实现中Vue会将模板编译成渲染函数,当然我们也可以不写模板,直接写渲染函数,以获得更好的控制. 渲染函数:渲染函数是用来生成Virtual DOM的: VNode虚拟节点: ...

  7. Vue 源码学习—Virtual DOM(虚拟 DOM)

    Virtual DOM Virtual DOM是什么 真实DOM成员 引入原因 作用 Virtual DOM是什么 定义: 普通的js对象来描述DOM对象,不是真实的DOM,顾名思义,称为虚拟DOM ...

  8. 虚拟DOM 之 Snabbdom 一、基本介绍

    Snabbdom 接口介绍(Snabbdom@1.0.1) 官方文档 当前snabbdom版本为 @1.0.1.接口介绍在官方文档的基础上做扩展,新版本接口使用基本和@0.7.4差不多. Snabbd ...

  9. Virtual DOM 的实现原理

    Virtual DOM 的实现原理 Virtual DOM 的实现原理 什么是虚拟DOM 为什么要用虚拟DOM 虚拟 DOM 的作用和虚拟 DOM 库 虚拟 DOM 的作用 虚拟 DOM 库 Snab ...

最新文章

  1. 设立『自动驾驶虚拟仿真赛道』
  2. 在公司拿了奖,发了点奖金
  3. 如何编译sqlite-3.5.9
  4. Maven 动态Web的创建 及 Tomcat的启动
  5. poj 1191 棋盘分割(记忆化dp+递归)
  6. nginx tornado php,tornado+nginx+python 微信公众号接入配置
  7. 零基础学python实战-苦苦发愁学习Python?让你享受 7天 掌握Python的感觉
  8. MapXtreme2005中关于使用动画图层的一个方法
  9. paip.版本控制CVS-SVN-TFS总结
  10. 推荐一款安卓抓包工具(抓包精灵)
  11. JAVA代码走查审查规范
  12. 使用Contect.Handler显示提交过来的“编辑成功”的信息
  13. html组态插件_组态 web组态 组态插件 编辑器 工业组态 物联网组态 组态编辑器...
  14. Elasticsearch 7.X RESTful 风格 高级查询
  15. 树莓派4B EC20 查看4G信号强度
  16. Javascript的事件驱动
  17. IDEAD中如何使用scala
  18. 第一次发C语言文章居然在头条获得35的展现
  19. react 生命挂钩_秋田+ React挂钩=耸人听闻的国家管理食谱
  20. 鲜花就该插牛粪!研究证明美女嫁丑男婚姻更幸福(转)

热门文章

  1. require(os)
  2. 基于51单片机实现模拟IIC总线时序
  3. C#的静态方法与静态成员(转)
  4. 被上海爱立信录取,GL
  5. Asp.net网站的ClickOnce自动部署(2)-虚拟目录的配置
  6. JavaMelody 1.77.0 发布,Java 应用监控平台
  7. 表单验证自定义二选一
  8. kali入侵windows
  9. AWS EC2 Run Command特性新增多重云脚本
  10. java网络流传输,中文乱码问题。