react初探(Virtual DOM分析)

Think

在 react 之前我只接触过 mvvm 结构的框架。

mvvm 采用这样一种机制:只要在模版中声明视图组件是和什么状态进行绑定的,双向绑定引擎就会在状态更新的时候自动更新视图。

那对于更新视图有没有其他的实现呢?

我们还可以考虑当组件状态发生变化时,就使用模版引擎去渲染整个视图。用旧的视图去替换掉新的视图。但是这样做,就会出现大量的 DOM 操作。

而DOM操作是非常可怕的😨

var div = document.createElement("div");
var str = "";
for( var key in div ) {
  str = str + key + " ";
}
console.log(str);

《react初探(Virtual DOM分析)》 DOM操作是很可怕的

因此,我们考虑到当状态变化时,只更新发生变化的部分,避免更新整颗 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)。

算法实现

  1. 构建虚拟DOM ( element )

    • 用 JavaScript 来表示一个 DOM 节点时,只需要记录它的节点类型、属性,还有子节点。

    • render方法会根据tagName构建一个真正的DOM节点,然后设置这个节点的属性,最后递归地把自己的子节点也构建起来。

  2. 找出新旧 DOM 树的区别 ( diff )

    比较两棵DOM树的差异是 Virtual DOM 算法最核心的部分。两个树的完全的 diff 算法是一个时间复杂度为 O(n^3) 的问题。但是在前端当中,你很少会跨越层级地移动DOM元素。所以 Virtual DOM 只会对同一个层级的元素进行对比:

    《react初探(Virtual DOM分析)》

    及第一层的 div 只会与第一层的 div 对比,第二层的 div 只会与第二层的 div 对比。这样算法复杂度就可以降到 O(n)。

    • 深度优先遍历,记录差异

    • 差异类型

    • 列表对比算法

  3. 将差异运用到真正的 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:

  1. 如何实现一个 Virtual DOM 算法https://github.com/livoras/blog/issues/13
  2. Virtual DOM dom&&diff 算法实现http://f2e.souche.com/blog/react-vitural-dom-diffsuan-fa-wei-dai-ma-shi-xian/
  3. 一个比较深刻的react.js源码分析http://purplebamboo.github.io/2015/09/15/reactjs_source_analyze_part_one/
    原文作者:Beginning丶2015
    原文地址: https://www.jianshu.com/p/4952e23ba08e
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞