简单-理解 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
Snabbdom

这里也推荐给感兴趣且还没有读过两个库源码的同学。
由于只关注 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,这个过程就是渲染了:

JavaScript

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

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

JavaScript

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

根据 Virtual Node 创建真实 DOM 元素,然后再追加到页面上。
再来看更新。virtual-dom 有明确的两步操作,首先 diff,然后 patch:

JavaScript

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

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

JavaScript

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同步更新!所以,这的确不矛盾啊。
第二个而且,技术本身不是目的,能够更好地解决问题才是王道嘛。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,265评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,274评论 1 288
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,087评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,479评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,782评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,218评论 1 207
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,594评论 2 309
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,316评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,955评论 1 237
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,274评论 2 240
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,803评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,177评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,732评论 3 229
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,953评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,687评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,263评论 2 267
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,189评论 2 258

推荐阅读更多精彩内容

  • 前言 React 好像已经火了很久很久,以致于我们对于 Virtual DOM 这个词都已经很熟悉了,网上也有非常...
    NARUTO_86阅读 17,067评论 6 65
  • 参考文章:深度剖析:如何实现一个Virtual DOM 算法 作者:戴嘉华React中一个没人能解释清楚的问题——...
    waka阅读 5,886评论 0 21
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,537评论 25 707
  • 为什么我们需要UI框架? 响应式编程提出两个最重要的观点是:系统应该是事件驱动并且响应状态的变化。DOM的UI组件...
    苏星河阅读 2,318评论 4 6
  • “天迢迢独渡日月,地邈邈唯载山河。星宿鬼魅隐道外,破天机者坐此阁。”孙道士吟着他的招牌话,来到了路边的小客栈。 跨...
    闲捧竹阅读 655评论 0 0