虚拟DOM 和 diff 算法 ——— 感受 diff 算法(第一次上树)
一、感受 diff 算法
当父节点发生改变时,比如 ul 变为 ol ,里面的 li 不发生改变,diff 算法是会暴力删除的。
2. diff 算法处理新旧节点不是同一个节点时。
snabbdom 判断是否是相同的虚拟节点:
创建节点时,所有子节点需要递归创建的。
二、手写第一次上树时
1. 目录结构:
// index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button id='btn'>点我改变DOM</button>
<div id="container"></div>
<script src="/virtual/bundle.js"></script>
</body>
</html>
// index.js
import h from './mySnabbdom/h'
import patch from './mySnabbdom/patch'
// var myVnode1 = h('div', {}, 'test')
// var myVnode1 = h('div', {}, [])
// var myVnode1 = h('div', {}, h())
// var myVnode1 = h('div', {}, [
// h('div', {}, '菠萝'),
// h('div', {}, '香蕉'),
// h('div', {}, [
// h('div', {}, '火龙果'),
// h('div', {}, '牛油果'),
// ]),
// ])
const container = document.getElementById('container');
const btn = document.getElementById('btn');
var myVnode1 = h('ul', {}, [
h('li', {}, '火龙果'),
h('li', {}, [
h('div', {}, [
h('ol', {}, [
h('li', {}, '哈哈哈'),
h('li', {}, '嘿嘿嘿'),
h('li', {}, '呵呵呵'),
]),
]),
]),
]);
patch(container, myVnode1);
var myVnode2 = h('div', {}, [
h('h1', {}, '你好'),
h('h2', {}, '再见'),
]);
btn.onclick = function() {
patch(myVnode1, myVnode2);
};
// patch.js
import vnode from './vnode'
import createElement from './createElement';
function isVnode(vnode) {
return typeof(vnode.sel) !== 'undefined';
}
function isSameNode(oldVnode, newVnode) {
return oldVnode.key === newVnode.key && oldVnode.sel === newVnode.sel;
}
export default function(oldVnode, newVnode) {
// 判断 oldVnode 是虚拟节点还是 DOM 节点
if (!isVnode(oldVnode)) {
// oldVnode 是 container 节点,tagName 属性是大写
// 如果是 DOM 节点,就包装成虚拟节点
oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, [], undefined, oldVnode);
// sel, data, children, text, elm
}
// 判断 oldVnode 和 newVnode 是不是同一个节点 (key和 选择器sel 相同)
if (isSameNode(oldVnode, newVnode)) {
console.log('是同一个节点');
} else {
// 暴力插入新的,删除旧的
let newVnodeElm = createElement(newVnode, oldVnode.elm);
// 插入到老节点之前
if (oldVnode.elm.parentNode && newVnodeElm) {
oldVnode.elm.parentNode.insertBefore(newVnodeElm, oldVnode.elm);
}
// 删除老节点
oldVnode.elm.parentNode.removeChild(oldVnode.elm);
}
}
// createElement.js
// 真正创建节点,将 vnode 创建为 DOM, 孤儿节点,不插入
export default function createElement(vnode) {
// 标杆:pivot,在标杆前面插入新节点 vnode
let domNode = document.createElement(vnode.sel);
// 有子节点还是文本
if (vnode.text !== '' && (vnode.children == undefined || vnode.children.length == 0)) {
// 内部是文字
domNode.innerText = vnode.text;
} else if (Array.isArray(vnode.children) && vnode.children.length) {
vnode.children.forEach(item => {
let childVnodeElm = createElement(item);
domNode.appendChild(childVnodeElm);
})
}
vnode.elm = domNode; // 补充 elm 属性
return vnode.elm;
}
2. 界面效果:
生活是痛苦的白天,死亡是凉爽的夜晚。