Think
在 react 之前我只接触过 mvvm 结构的框架。
mvvm 采用这样一种机制:只要在模版中声明视图组件是和什么状态进行绑定的,双向绑定引擎就会在状态更新的时候自动更新视图。
那对于更新视图有没有其他的实现呢?
我们还可以考虑当组件状态发生变化时,就使用模版引擎去渲染整个视图。用旧的视图去替换掉新的视图。但是这样做,就会出现大量的 DOM 操作。
而DOM操作是非常可怕的😨
var div = document.createElement("div");
var str = "";
for( var key in div ) {
str = str + key + " ";
}
console.log(str);
因此,我们考虑到当状态变化时,只更新发生变化的部分,避免更新整颗 DOM 树。
Virtual DOM
react 的核心概念便是 Virtual DOM。
相较于上面的原生 DOM 事件,使用原生 Javascript 来实现就更快,更简单。
var element = {
tagName: 'ul', // 节点标签名
props: { // DOM的属性,用一个对象存储键值对
id: 'list'
},
children: [ // 该节点的子节点
{tagName: 'li', props: {class: 'item'}, children: ["Item 1"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 2"]},
{tagName: 'li', props: {class: 'item'}, children: ["Item 3"]},
]
}
对应的 HTML 结构为:
<ul id='list'>
<li class='item'>Item 1</li>
<li class='item'>Item 2</li>
<li class='item'>Item 3</li>
</ul>
既然 javascript 可以用来表示 dom ,那就可以通过 javascript 的树结构来构成一颗 dom 树。
这样,在发生状态变更时,我们可以通过对比旧的树和新的树,来记录差异。只针对差异部分进行 dom 操作。这样,页面更新了,而 dom 操作也只变更了不同的地方。
Virtual DOM 本质上就是在 JS 和 DOM 之间做了一个缓存。可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM)。
算法实现
-
构建虚拟DOM ( element )
用 JavaScript 来表示一个 DOM 节点时,只需要记录它的节点类型、属性,还有子节点。
-
render
方法会根据tagName
构建一个真正的DOM节点,然后设置这个节点的属性,最后递归地把自己的子节点也构建起来。
-
找出新旧 DOM 树的区别 ( diff )
比较两棵DOM树的差异是 Virtual DOM 算法最核心的部分。两个树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题。但是在前端当中,你很少会跨越层级地移动DOM元素。所以 Virtual DOM 只会对同一个层级的元素进行对比:
及第一层的 div 只会与第一层的 div 对比,第二层的 div 只会与第二层的 div 对比。这样算法复杂度就可以降到 O(n)。
深度优先遍历,记录差异
差异类型
-
列表对比算法
-
将差异运用到真正的 DOM 树上 ( patch )
我们可以对 DOM 树进行深度优先的遍历,遍历的时候从生成的
patches
对象中找出当前遍历的节点差异,然后进行 DOM 操作。
// 1. 构建虚拟DOM
var tree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: blue'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li')])
])
// 2. 通过虚拟DOM构建真正的DOM
var root = tree.render()
document.body.appendChild(root)
// 3. 生成新的虚拟DOM
var newTree = el('div', {'id': 'container'}, [
el('h1', {style: 'color: red'}, ['simple virtal dom']),
el('p', ['Hello, virtual-dom']),
el('ul', [el('li'), el('li')])
])
// 4. 比较两棵虚拟DOM树的不同
var patches = diff(tree, newTree)
// 5. 在真正的DOM元素上应用变更
patch(root, patches)
Mark:
- 如何实现一个 Virtual DOM 算法https://github.com/livoras/blog/issues/13
- Virtual DOM dom&&diff 算法实现http://f2e.souche.com/blog/react-vitural-dom-diffsuan-fa-wei-dai-ma-shi-xian/
- 一个比较深刻的react.js源码分析http://purplebamboo.github.io/2015/09/15/reactjs_source_analyze_part_one/