这一节,依然是深入剖析Vue源码系列,上几节内容介绍了
Virtual DOM
是Vue在渲染机制上做的优化,而渲染的核心在于数据变化时,如何高效的更新节点,这就是diff算法。由于源码中关于diff
算法部分流程复杂,直接剖析每个流程不易于理解,所以这一节我们换一个思路,参考源码来手动实现一个简易版的diff
算法。
之前讲到Vue
在渲染机制的优化上,引入了Virtual DOM
的概念,利用Virtual DOM
描述一个真实的DOM
,本质上是在JS
和真实DOM
之间架起了一层缓冲层。当我们通过大量的JS
运算,并将最终结果反应到浏览器进行渲染时,Virtual DOM
可以将多个改动合并成一个批量的操作,从而减少 dom
重排的次数,进而缩短了生成渲染树和绘制节点所花的时间,达到渲染优化的目的。之前的章节,我们简单的介绍了Vue
中Vnode
的概念,以及创建Vnode
到渲染Vnode
再到真实DOM
的过程。如果有忘记流程的,可以参考前面的章节分析。
**从render
函数到创建虚拟DOM
,再到渲染真实节点,这一过程是完整的,也是容易理解的。然而引入虚拟DOM
的核心不在这里,而在于当数据发生变化时,如何最优化数据变动到视图更新的过程。这一个过程才是Vnode
更新视图的核心,也就是常说的diff
算法。**下面跟着我来实现一个简易版的diff
算法
8.1 创建基础类
代码编写过程会遇到很多基本类型的判断,第一步需要先将这些方法封装。
class Util {
constructor() {
}
// 检测基础类型
_isPrimitive(value) {
return (typeof value === 'string' || typeof value === 'number' || typeof value === 'symbol' || typeof value === 'boolean')
}
// 判断值不为空
_isDef(v) {
return v !== undefined && v !== null
}
}
// 工具类的使用
const util = new Util()
8.2 创建Vnode
Vnode
这个类在之前章节已经分析过源码,本质上是用一个对象去描述一个真实的DOM
元素,简易版关注点在于元素的tag
标签,元素的属性集合data
,元素的子节点children
,text
为元素的文本节点,简单的描述类如下:
class VNode {
constructor(tag, data, children) {
this.tag = tag;
this.data = data;
this.children = children;
this.elm = ''
// text属性用于标志Vnode节点没有其他子节点,只有纯文本
this.text = util._isPrimitive(this.children) ? this.children : ''
}
}
8.3 模拟渲染过程
接下来需要创建另一个类模拟将render
函数转换为Vnode
,并将Vnode
渲染为真实DOM
的过程,我们将这个类定义为Vn
,Vn
具有两个基本的方法createVnode, createElement
, 分别实现创建虚拟Vnode
,和创建真实DOM
的过程。
8.3.1 createVnode
createVnode
模拟Vue
中render
函数的实现思路,目的是将数据转换为虚拟的Vnode
,先看具体的使用和定义。
// index.html
<script src="diff.js">
<script>
// 创建Vnode
let createVnode = function() {
let _c = vn.createVnode; return _c('div', {
attrs: {
id: 'test' } }, arr.map(a => _c(a.tag, {
}, a.text)))}
// 元素内容结构
let arr = [{
tag: 'i', text: 2
}, {
tag: 'span', text: 3
}, {
tag: 'strong', text: 4
}]
</script>
// diff.js
(function(global) {
class Vn {
constructor() {
}