说明
浏览器工作原理与实践专栏学习笔记
DOM 的缺陷
操作 DOM 触发样式计算、布局、绘制、栅格化、合成等任务,还有可能引起重绘或者合成操作。另外,对于 DOM 的不当操作还有可能引发强制同步布局和布局抖动的问题,这些操作都会大大降低渲染效率。
什么是虚拟 DOM
虚拟 DOM 要解决什么事情
将页面改变的内容应用到虚拟 DOM 上,而不是直接应用到 DOM 上。
变化被应用到虚拟 DOM 上时,虚拟 DOM 并不急着去渲染页面,而仅仅是调整虚拟 DOM 的内部状态,这样操作虚拟 DOM 的代价就变得非常轻了。
在虚拟 DOM 收集到足够的改变时,再把这些变化一次性应用到真实的 DOM 上。
虚拟 DOM 执行流程
结合 React 流程画的一张虚拟 DOM 执行流程图
创建阶段:首先依据 JSX 和基础数据创建出来虚拟 DOM,它反映了真实的 DOM 树的结构。然后由虚拟 DOM 树创建出真实 DOM 树,真实的 DOM 树生成完后,再触发渲染流水线往屏幕输出页面。
更新阶段:如果数据发生了改变,那么就需要根据新的数据创建一个新的虚拟 DOM 树;然后 React 比较两个树,找出变化的地方,并把变化的地方一次性更新到真实的 DOM 树上;最后渲染引擎更新渲染流水线,并生成新的页面。
React Fiber 更新机制
通过上图可以知道,当有数据更新时,React 会生成一个新的虚拟 DOM,然后拿新的虚拟 DOM 和之前的虚拟 DOM 进行比较,这个过程会找出变化的节点,然后再将变化的节点应用到 DOM 上。
重点关注下比较过程,最开始的时候,比较两个虚拟 DOM 的过程是在一个递归函数里执行的,其核心算法是 reconciliation。通常情况下,这个比较过程执行得很快,不过当虚拟 DOM 比较复杂的时候,执行比较函数就有可能占据主线程比较久的时间,这样就会导致其他任务的等待,造成页面卡顿。为了解决这个问题,React 团队重写了 reconciliation 算法,新的算法称为 Fiber reconciler,之前老的算法称为 Stack reconciler。
关于协程可以看看之前的笔记:浏览器原理 19 # JavaScript 引擎是如何实现 async / await 以同步的方式来编写异步代码的?
协程的另外一个称呼就是 Fiber,所以在这里我们可以把 Fiber 和协程关联起来,那么所谓的 Fiber reconciler 就是在执行算法的过程中出让主线程,这样就解决了 Stack reconciler 函数占用时间过久的问题。
双缓存视角看虚拟 DOM
比如:一幅完整的画面,可能需要计算多次才能完成,如果每次计算完一部分图像,就将其写入缓冲区,那么就会造成一个后果,那就是在显示一个稍微复杂点的图像的过程中,你看到的页面效果可能是一部分一部分地显示出来,因此在刷新页面的过程中,会让用户感受到界面的闪烁。
而使用双缓存,可以让你先将计算的中间结果存放在另一个缓冲区中,等全部的计算结束,该缓冲区已经存储了完整的图形之后,再将该缓冲区的图形数据一次性复制到显示缓冲区,这样就使得整个图像的输出非常稳定。
双缓存是一种经典的思路,应用在很多场合,能解决页面无效刷新和闪屏的问题,虚拟 DOM 就是双缓存思想的一种体现。你可以把虚拟 DOM 看成是 DOM 的一个 buffer,和图形显示一样,它会在完成一次完整的操作之后,再把结果应用到 DOM 上,这样就能减少一些不必要的更新,同时还能保证 DOM 的稳定输出。
MVC 模式中虚拟 DOM 所扮演的角色
MVC 是一个非常重要且应用广泛的模式,因为它能将数据和视图进行分离,在涉及到一些复杂的项目时,能够大大减轻项目的耦合度,使得程序易于维护。
MVC 的基础结构:
MVC 的整体结构比较简单,由模型、视图和控制器组成,其核心思想就是将数据和视图分离,也就是说视图和模型之间是不允许直接通信的,它们之间的通信都是通过控制器来完成的。
基于 React 和 Redux 构建 MVC 模型:
在该图中,可以把虚拟 DOM 看成是 MVC 的视图部分,其控制器和模型都是由 Redux 提供的。
其具体实现过程如下:
图中的控制器是用来监控 DOM 的变化,一旦 DOM 发生变化,控制器便会通知模型,让其更新数据;
模型数据更新好之后,控制器会通知视图,告诉它模型的数据发生了变化;
视图接收到更新消息之后,会根据模型所提供的数据来生成新的虚拟 DOM;
新的虚拟 DOM 生成好之后,就需要与之前的虚拟 DOM 进行比较,找出变化的节点;
比较出变化的节点之后,React 将变化的虚拟节点应用到 DOM 上,这样就会触发 DOM 节点的更新;
DOM 节点的变化又会触发后续一系列渲染流水线的变化,从而实现页面的更新。
分析基于 React 或者 Vue 这些前端框架时,先重点把握大的 MVC 骨架结构,然后再重点查看通信方式和控制器的具体实现方式。
拓展
其实虚拟 DOM 主要是一个抽象层,用于描述视图,抽象视图变化,诞生主要目的是跨平台。所以才有 React Native,weex。解决 js 操作 dom 性能问题只是附带的,而且虚拟 DOM 是有代价的,在页面结构简单操作也简单的情况下相对 js 操作 DOM 并不一定会有优势。
另外感兴趣的可以看:
snabbdom
可以看一下这篇文章:Virtual Dom库snabbdom代码解析
可以从下面三个方向稍微了解一下 snabbdom
第一个是js模拟的dom节点vnode的结构
第二个是diff算法
第三个是有了diff如何打patch的