WuShaolin

悟已往之不谏,知来者之可追!

0%

虚拟DOM

学习底层的虚拟DOM原理

二话不说,直接上代码

基本的代码如下:

let nodeData = {
  tag: 'div',
  children: [{
      tag: 'p',
      children: [{
        tag: 'span',
        children: [{
          tag: '#text',
          text: 'wushao.xyz'
        }]
      }]
    },
    {
      tag: 'span',
      children: [{
        tag: '#text',
        text: 'wushao.com'
      }]
    }
  ]
}

/* 
等同于这段html代码
<div>
  <p>
    <span>wushao.xyz</span>
  </p>
  <span>wushao.com</span>
</div> 
*/

/**
 * ES6的写法
 */
class VNode {
  constructor(tag, children, text) {
    this.tag = tag
    this.children = children
    this.text = text
  }

  render() {
    if (this.tag === '#text') {
      return document.createTextNode(this.text)
    }
    let el = document.createElement(this.tag)
    this.children.forEach(vChild => {
      el.appendChild(vChild.render())
    });
    return el
  }
}

/**
 * 创建虚拟dom节点的简便函数
 * @param {标签名} tag 
 * @param {子标签} children 
 * @param {文本值} text 
 */
function v(tag, children, text) {
  //如果没有tag,第二个参数是字符串的话
  if (typeof children === 'string') {
    text = children
    children = []
  }
  return new VNode(tag, children, text)
}

/**
 * ES5的写法
 * @param {*} tag 
 * @param {*} children 
 * @param {*} text 
 */
function vNode(tag, children, text) {
  this.tag = tag
  this.children = children
  this.text = text
}
vNode.prototype.render = function () {
  if (this.tag === '#text') {
    return document.createTextNode(this.text)
  }
  let el = document.createElement(this.tag)
  this.children.forEach(vChild => {
    el.appendChild(vChild.render())
  });
  return el
}

//举例说明
let vNode1 = v('div', [
  v('p', [
    v('span', [v('#text', 'wushao.xyz')])
  ]),
  v('span', [
    v('#text', 'wushao.com')
  ])
])
console.log(vNode1);

const root = document.querySelector('#root')
root.appendChild(vNode1.render()) //把虚拟的dom映射进了真实的dom结构里面

let vNode2 = v('div', [
  v('p', [
    v('span', [v('#text', 'wushao.xyz')])
  ]),
  v('span', [
    v('#text', 'wushao.com')
  ]),
  v('span', [
    v('#text', 'wushao')
  ])
])

document.querySelector('#change').onclick = function () {
  root.innerHTML = ''
  root.appendChild(vNode2.render())
}

/**
 * 比较前后两个节点
 * @param {要比较的DOM结构} parent 
 * @param {旧的节点} newVNode 
 * @param {*新的节点 oldNode 
 * @param {索引} index 
 */
function patchElement(parent, newVNode, oldNode, index = 0) {
  if (!oldNode) {
    //新增元素
    parent.appendChild(newVNode.render())
  } else if (!newVNode) {
    parent.removeChild(parent.childNodes[index])
  } else if (newVNode.tag !== oldNode.tag || newVNode.text != oldNode.text){
    parent.replaceChild(newVNode.render(), parent.childNodes[index])
  } else {
    for (let i = 0; i < newVNode.children.length || i < oldNode.children.length; i++) {
      patchElement(parent.childNodes[index], newVNode.children[i], oldNode.children[i], i)
    }
  }

}

可以在HTML中直接测试

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>虚拟DOM</title>
</head>
<body>
  <div id="root">

  </div>
  <button id="change">change</button>
  <script src="./v-dom.js"></script>
</body>
</html>