前言

React 好像已经火了很久很久,以致于我们对于 Virtual DOM 这个词都已经很熟悉了,网上也有非常多的介绍 React、Virtual DOM 的文章。但是直到前不久我专门花时间去学习 Virtual DOM,才让我对 Virtual DOM 有了一定的理解,以致于要怀疑起很久之前看过的那些文章来。倒不是这些文章讲得不对,而是现在在我看来角度不太好,说得越多,越说不清。

让我能够有所开窍(自认为)的,是这篇文章:


Change And Its Detection In JavaScript Frameworks
Monday Mar 2, 2015 by Tero Parviainen
http://teropa.info/blog/2015/03/02/change-and-its-detection-in-javascript-frameworks.html


作者看问题的角度很棒,从数据变更与UI同步的角度来介绍各个典型框架,特别是对于 React 的 Virtual DOM,从这个角度理解起来更容易些。

感兴趣的同学,如果没有读过这篇文章,推荐去看一看,不感兴趣就算了。不过接下来我要讲的东西,部分整理自这篇文章,特别是从这篇文章中引用的图片,非常棒。当然还有我自己的一些思考,以及一些对于目前 Virtual DOM 实现的开源库的分析。

如果读了上面推荐的这篇文章,我倒是不介意你不再继续把本文读下去,因为有些东西你已经领会到了。当然,也不反对。

变化这件事

谈论页面的变化之前,咱们先看下数据和页面(视觉层面的页面)的关系。数据是隐藏在页面底下,通过渲染展示给用户。同样的数据,按照不同的页面设计和实现,会以不同形式、样式的页面呈现出来。有时候在一个页面内的不同位置,也会有相同数据的不同表现。

 
Paste_Image.png

Web 的早期,这些页面通常是静态的,页面内容不会变化。而如果数据发生了变化,通常需要重新请求页面,得到基于新的数据渲染出的新的页面。

 
Paste_Image.png

至少,这个模式理解起来挺简单不是吗。

直到 Web 应用复杂起来,开发者们开始关注用户体验,开始将大量的处理向前端迁移,页面变得动态、灵活起来。一个显著的特征是,数据发生变化之后,不再需要刷新页面就能看到页面上的内容随之更新了。

前端需要做的事情变得多了起来,前端工程师们也就修炼了起来,各种前端技术也就出现了。

首先,聪明的工程师们发现既然是在前端渲染页面,如果只是部分数据发生了变化,就要把页面整体或一大块区域重新渲染就有点笨了。为什么不把事情做得更极致些,只更新变化的数据对应的页面的内容呢?

怎么做呢?操作 DOM 呗。DOM 就是浏览器提供给开发者用于操作页面的模型嘛,直接通过脚本来调用 DOM 的各种接口就 OK 了。而且我们还有了像 jQuery 这样的棒棒的工具,操作 DOM 变得 so easy。

然而,页面越来越复杂,聪明的工程师们发现数据变化之后,老是需要手动编码去操作对应的 DOM 节点执行更新,有点烦,不够懒啊。于是各种框架如雨后春笋般出现了,纷纷表示可以简化这个过程。

稍微早期的框架有这样的:

 
Paste_Image.png

开发者借助框架,监听数据的变更,在数据变更后更新对应的 DOM 节点。虽然还是要写一些代码,但是写出来的代码好像很有条理的样子,至少更容易理解和维护了,也不错嘛。

更进一步,MVVM 框架出现了,以 AngularJS 为代表:

 
Paste_Image.png

仍然是数据变化后更新对应 DOM 节点的方式,但是建立这种绑定关系的过程被框架所处理,开发者要写的代码变少了,而且代码更易读和维护了。

再然后呢,大家就在这个棒棒的模式上继续深耕,纷纷表示还可以在性能上做得更好,前端领域一片繁荣。

再后来 React 出现了,它不仅不是 MVVM 框架,甚至连 MV* 框架都不是。这年头,不是个 MV* 框架还好意思出门?可 React 还真的带来了新的思路!

什么思路呢?

就是回到过去,回到那个简单而美好的时候。具体而言,就是每次数据发生变化,就重新执行一次整体渲染。的确这样更简单,不用去琢磨到底是数据的哪一部分变化了,需要更新页面的哪一部分。但是坏处太明显,体验不好啊。而 React 给出了解决方案,就是 Virtual DOM。

Virtual DOM 概况来讲,就是在数据和真实 DOM 之间建立了一层缓冲。对于开发者而言,数据变化了就调用 React 的渲染方法,而 React 并不是直接得到新的 DOM 进行替换,而是先生成 Virtual DOM,与上一次渲染得到的 Virtual DOM 进行比对,在渲染得到的 Virtual DOM 上发现变化,然后将变化的地方更新到真实 DOM 上。

简单来说,React 在提供给开发者简单的开发模式的情况下,借助 Virtual DOM 实现了性能上的优化,以致于敢说自己“不慢”。

Virtual DOM

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

 
React - 初始渲染

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

 
React - 数据更新

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

Virtual DOM 作为数据结构,需要能准确地转换为真实 DOM,并且方便进行对比。除了 Virtual DOM 外,React 还实现了其他的特性,为了专注于 Virtual DOM,我另外找了两个比较 Virtual DOM 来学习:

这里也推荐给感兴趣且还没有读过两个库源码的同学。

由于只关注 Virtual DOM,通过阅读两个库的源码,对于 Virtual DOM 的定位有了更深一步的理解。

首先看数据结构。

** Virtual DOM 数据结构 **

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

在 virtual-dom 中,给 Virtual Node 声明了对应的类 VirtualNode,基本是用于存储数据,包括:

  • tagName
  • properties
  • children
  • key
  • namespace
  • count
  • hasWidgets
  • hasThunks
  • hooks
  • descendantHooks

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

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

虽然有所差别,除去实现上的差别和库本身的额外特性,可以看到 Virtual Node 用于创建真实节点的数据包括:

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

有了这些其实就可以创建对应的真实节点了。

创建 Virtual DOM

嵌套 Virtual Node 就可以得到一棵树了。virtual-dom 和 Snabbdom 都提供了函数调用的方式来创建 Virtual Tree,这个过程就是渲染了:

var vTree = h('div', [
h('span', 'hello'),
h('span', 'world')
])

React 提供 JSX 这颗糖,使得我们可以用类似 HTML 的语法来编写,不过编译后实质还是通过函数调用来得到一棵嵌套的 Virtual Tree。而且这对于理解 Virtual DOM 机制来说不是特别重要,先不管这个。

使用 Virtual DOM

首先来看初始化,virtual-dom 提供了 createElement 函数:

var rootNode = createElement(tree)
document.body.appendChild(rootNode)

根据 Virtual Node 创建真实 DOM 元素,然后再追加到页面上。

再来看更新。virtual-dom 有明确的两步操作,首先 diff,然后 patch:

var newTree = render(count)
var patches = diff(tree, newTree)
rootNode = patch(rootNode, patches)

而 Snabbdom 则简单些,只有一个 patch 函数,内部在进行比对的同时将更新应用到了真实 DOM 上,而且初始化也是用的 patch 函数:

var vnode = render(data)
var container = document.getElementById('container')
patch(container, vnode) // after data changed
var newVnode = render(data)
patch(vnode, newVnode)

性能优化

关于性能优化,除了 Virtual DOM 机制本身提供的特性以外,再就是不同的 Virtual DOM 库自身的优化方案了,这个可以看上面两个库的文档,不再赘述。

其实提到 Virtual DOM 的差异比对,有人会对其内部如何处理数组感兴趣。的确,如果数组元素的位置发生了改变,这个要识别起来是有点麻烦。为此,上面两个库和 React 其实都在 Virtual Node 上额外记录了一个属性“key”,就是用来辅助进行 Virtual Node 的比对的。

简单来说,如果两个 Virtual Node 的位置不同,但是 key 属性相同,那么会将这两个节点视为由相同数据渲染得到的,然后进一步进行差异分析。所以,并不是仅仅按照位置进行比对,具体的实现可以查看各个库的源码。

小结

OK,以上就是我要讲的全部所有内容了。

相信很多同学之前对 Virtual DOM 已经很熟悉了,比我理解得更深入的同学相信也不会少。不过从“数据变化与UI同步更新”这个角度来理解 Virtual DOM,在我看来是比较好的,所以整理在这里了。

有个问题挺常见,AngularJS 和 React 哪个更好?

如果说各有千秋的话,估计大家就“呵呵”了。但是这两个框架/库从“数据变化与UI同步更新”的角度来看,的确都解决了问题,而且解决问题的方式大家都挺认可(至少在喜欢它们的同学眼里是这样的)。

而且,如果大家关注 Vue 的话,可以看到,这个 MVVM 框架已经发布了 2.0,其中就采用了 Virtual DOM 实现其UI同步更新!所以,这的确不矛盾啊。

第二个而且,技术本身不是目的,能够更好地解决问题才是王道嘛。

作者:luobo_tang
链接:https://www.jianshu.com/p/bef1c1ee5a0e

【转】Virtual DOM的更多相关文章

  1. 窥探Vue.js 2.0 - Virtual DOM到底是个什么鬼?

    引言 你可能听说在Vue.js 2.0已经发布,并且在其中新添加如了一些新功能.其中一个功能就是"Virtual DOM". Virtual DOM是什么 在之前,React和Em ...

  2. 个人对于Virtual DOM的一些理解

    之前一直认为react的Virtual DOM操作会比传统的操作DOM要快,这其实是错误的,React 从来没有说过 "React 比原生操作 DOM 快".如果没有 Virtua ...

  3. 抛开react,如何理解virtual dom和immutability

    去年以来,React的出现为前端框架设计和编程模式吹来了一阵春风.很多概念,无论是原本已有的.还是由React首先提出的,都因为React的流行而倍受关注,成为大家研究和学习的热点.本篇分享主要就聚焦 ...

  4. 深度剖析:如何实现一个 Virtual DOM 算法

    本文转载自:https://github.com/livoras/blog/issues/13 目录: 1 前言 2 对前端应用状态管理思考 3 Virtual DOM 算法 4 算法实现 4.1 步 ...

  5. Virtual DOM 算法

    前端 virtual-dom react.js javascript 目录: 1 前言 2 对前端应用状态管理思考 3 Virtual DOM 算法 4 算法实现 4.1 步骤一:用JS对象模拟DOM ...

  6. React v16-alpha 从virtual dom 到 dom 源码简读

    一.物料准备 1.克隆react源码, github 地址:https://github.com/facebook/react.git 2.安装gulp 3.在react源码根目录下: $npm in ...

  7. React源码解析-Virtual DOM解析

    前言:最近一直在研究React,看了陈屹先生所著的深入React技术栈,以及自己使用了这么长时间.对React应该说有比较深的理解了,正好前阵子也把两本关于前端设计模式的书看完了,总感觉有一种知识错综 ...

  8. virtual dom的实践

    最近基于virtual dom 写了一个小框架-aoy. aoy是一个轻量级的mvvm框架,基于Virtual DOM.虽然现在看起来很单薄,但我做了完善的单元测试,可以放心使用.aoy的原理可以说和 ...

  9. 深度理解 Virtual DOM

    目录: 1 前言 2 技术发展史 3 Virtual DOM 算法 4 Virtual DOM 实现 5 Virtual DOM 树的差异(Diff算法) 6 结语 7 参考链接 1 前言 我会尽量把 ...

  10. vue的Virtual Dom实现- snabbdom解密

    vue在官方文档中提到与react的渲染性能对比中,因为其使用了snabbdom而有更优异的性能. JavaScript 开销直接与求算必要 DOM 操作的机制相关.尽管 Vue 和 React 都使 ...

随机推荐

  1. Swift_字典详解

    Swift_字典详解 点击查看源码 初始化 fileprivate func testInit() { //空字典 var dic = [String:String]() print(dic) dic ...

  2. iOS 语言国际化配置

    所谓的iOS 国际化,就是根据系统不同的语言自动切换. 首先,如图:   创建一个.strings文件,以“Localizable.strings”为文件名:   创建完之后,在XCode的右边勾选自 ...

  3. 2822: [AHOI2012]树屋阶梯

    Time Limit: 1 Sec  Memory Limit: 128 MBSubmit: 1161  Solved: 694[Submit][Status][Discuss] Descriptio ...

  4. deepin15.7下使用apt安装mysql5.7不显示root密码设置的解决方法

    在安装MySQL的过程中,并没有要求设置root账户密码的步骤,导致很多人无法使用root账户登录 这个问题早已有解决方案,笔者在deepin15.7下安装也遇到同样问题,只是做一个简单的记录 解决思 ...

  5. 使用Python第三方库生成二维码

    本文主要介绍两个可用于生成二维码的Python第三方库:MyQR和qrcode. MyQR的使用: 安装: pip install MyQR 导入: from MyQR import myqr imp ...

  6. CentOS7下PHP7.2安装redis扩展

    1.安装phpize(存在忽略) yum install php-devel 2.下载扩展源码包,直接用wget,一般放在 /usr/local/src wget https://github.com ...

  7. React中的全选反选问题

    全选反选问题 1.在state里维护一个数组,例如showArr:[] 2.绑定点击事件的时候将当前这个当选按钮的index加进来 <span className='arrow' onClick ...

  8. 点按钮ajax get方法修改0或1状态封装成函数

    最终效果 列表页面表格里点击按钮修改状态 按钮样式要引入bootstrap才可以用 本文件用的是laravel框架环境 larave路由里 Route::get('category/changesta ...

  9. Learning Experience of Big Data: Learn to install CentOs 6.5 on my laptop

    I have learnt some experience about Big Data during my summer vocation,I was told that The first thi ...

  10. 3.从print到I/O

    为何对双引号念念不忘? >>> print("hello, world!") hello, world!   平x而论,既然在意双引号的去掉,为何不在意括号的去掉 ...