虚拟DOM的实现

2017-08-24  本文已影响0人  野薇薇

[维护状态,更新视图]
用js对象表示Dom元素
js:

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"]},
  ]
}

表示dom结构为:

<ul id='list'>
  <li class='item'>Item 1</li>
  <li class='item'>Item 2</li>
  <li class='item'>Item 3</li>
</ul>
Virtual DOM 算法,包括几个步骤:

算法实现

1. js对象模拟dom元素

js对象表示DOM元素 需要记录的信息有:节点类型、属性,子节点
element.js

function Element (tagName, props, children) {
  this.tagName = tagName
  this.props = props
  this.children = children
}

module.exports = function (tagName, props, children) {
  return new Element(tagName, props, children)
}

例如上面的 DOM 结构就可以简单的表示:

var el = require('./element')

var ul = el('ul', {id: 'list'}, [
  el('li', {class: 'item'}, ['Item 1']),
  el('li', {class: 'item'}, ['Item 2']),
  el('li', {class: 'item'}, ['Item 3'])
])

现在ul只是一个 JavaScript 对象表示的 DOM 结构,页面上并没有这个结构。我们可以根据这个ul构建真正的<ul>:

Element.prototype.render = function () {
  var el = document.createElement(this.tagName) // 根据tagName构建
  var props = this.props

  for (var propName in props) { // 设置节点的DOM属性
    var propValue = props[propName]
    el.setAttribute(propName, propValue)
  }

  var children = this.children || []

  children.forEach(function (child) {
    var childEl = (child instanceof Element)
      ? child.render() // 如果子节点也是虚拟DOM,递归构建DOM节点
      : document.createTextNode(child) // 如果字符串,只构建文本节点
    el.appendChild(childEl)
  })

  return el
}

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

var ulRoot = ul.render()
document.body.appendChild(ulRoot)

上面的ulRoot是真正的DOM节点,把它塞入文档中,这样body里面就有了真正的<ul>的DOM结构:

<ul id='list'>
  <li class='item'>Item 1</li>
  <li class='item'>Item 2</li>
  <li class='item'>Item 3</li>
</ul>

2.比较两颗dom树的差异

diff算法是重点

var oldStr = 'aaabbbccc';
var newStr = 'aaagggccc';
diff信息:[3, "-3", "+ggg", 3]

整数代表无变化的字符数量,“-”开头的字符串代表被移除的字符数量,“+”开头的字符串代表新加入的字符。所以我们可以写一个 minimizeDiffInfo 函数:

function minimizeDiffInfo(originalInfo){
    var result = originalInfo.map(info => {
        if(info.added){
            return '+' + info.value;
        }
        if(info.removed){
            return '-' + info.count;
        }
        return info.count;
    });
    return JSON.stringify(result);
}
 
var diffInfo = [
    { count: 3, value: 'aaa' },
    { count: 3, added: undefined, removed: true, value: 'bbb' },
    { count: 3, added: true, removed: undefined, value: 'ggg' },
    { count: 3, value: 'ccc' }
];
minimizeDiffInfo(diffInfo);
//=> '[3, "-3", "+ggg", 3]'

用户端接受到精简之后的 diff 信息,生成最新的资源:

mergeDiff('aaabbbccc', '[3, "-3", "+ggg", 3]');
//=> 'aaagggccc'
 
function mergeDiff(oldString, diffInfo){
    var newString = '';
    var diffInfo = JSON.parse(diffInfo);
    var p = 0;
    for(var i = 0; i < diffInfo.length; i++){
        var info = diffInfo[i];
        if(typeof(info) == 'number'){
            newString += oldString.slice(p, p + info);
            p += info;
            continue;
        }
        if(typeof(info) == 'string'){
            if(info[0] === '+'){
                var addedString = info.slice(1, info.length);
                newString += addedString;
            }
            if(info[0] === '-'){
                var removedCount = parseInt(info.slice(1, info.length));
                p += removedCount;
            }
        }
    }
    return newString;
}

3.把差异应用到真正的DOM树上

结语

虚拟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)

参考:深度剖析:如何实现一个 Virtual DOM 算法

上一篇 下一篇

猜你喜欢

热点阅读