虚拟DOM及diff算法学习
h.js
import vnode from "./vnode";
export default function h(sel,data,c){
if(arguments.length!=3){
throw new Error('参数数量错误');
}
if(typeof c=='string'||typeof c=='number'){
// console.log(123,c);
return vnode(sel,data,undefined,c,undefined);
}
else if(Array.isArray(c)){
let children=[];
for(let i =0;i<c.length;i++){
if(!(typeof c[i]=='object' && c[i].hasOwnProperty('sel'))){
throw new Error('参数格式错误')
}
// console.log(c[i]);
children.push(c[i]);
}
// console.log(111,vnode(sel,data,children,undefined,undefined));
return vnode(sel,data,children,undefined,undefined);
}
else if(typeof c=='object' && c.hasOwnProperty('sel')){
let children=[c]
// console.log(222,vnode(sel,data,children,undefined,undefined));
return vnode(sel,data,children,undefined,undefined);
}
else{
throw new Error('参数格式错误')
}
}
vnode.js
export default function(sel,data,children,text,elm){
const key = data === undefined ? undefined : data.key
return {
sel,data,children,text,elm,key
}
}
path.js
import vnode from "./vnode";
import createElement from "./createElement";
import pathVonde from "./pathVnode";
export default function path(oldVnode, newVonde) {
if (oldVnode.sel == '' || oldVnode.sel == undefined) {
oldVnode = vnode(oldVnode.tagName.toLowerCase(), {}, [], undefined, oldVnode);
}
if (oldVnode.sel == newVonde.sel && oldVnode.key == newVonde.key) {
console.log('yes');
pathVonde(oldVnode,newVonde);
}
else {
console.log('no');
let newVondeElm = createElement(newVonde, oldVnode.elm);
// 插入老节点之前
if (oldVnode.elm.parentNode && newVondeElm) {
oldVnode.elm.parentNode.insertBefore(newVondeElm, oldVnode.elm)
}
// 删除老节点
oldVnode.elm.parentNode.removeChild(oldVnode.elm)
}
}
createElement.js
export default function createElement(vnode,pivot){
// console.log(vnode,pivot);
// 创建一个DOM节点
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>0){
// 子节点
for(let i =0;i< vnode.children.length;i++){
let ch=vnode.children[i];
let chdom=createElement(ch);
domNode.appendChild(chdom);
}
}
// 补充elm属性
vnode.elm=domNode;
return vnode.elm
}
pathVnode.js
import createElement from'./createElement.js';
import updateChildren from './updateChildren.js';
export default function pathVonde(oldVnode,newVonde){
// 新旧一样
if (oldVnode === newVonde) {
console.log('新旧一样');
return
}
// 新vnode是否含有text
if (newVonde.text != '' && (newVonde.children == undefined || newVonde.children.length == 0)) {
console.log('新vnode含有text');
// 含有text
// console.log(oldVnode,newVonde);
if (oldVnode.text != newVonde.text) {
oldVnode.elm.innerText = newVonde.text;
}
}
else {
console.log('不含有text');
// 不含有text
// 老节点是否含有children
if (oldVnode.children != undefined && oldVnode.children.length > 0) {
console.log('老节点含有children');
// 含有children
// console.log(oldVnode,newVonde);
updateChildren(oldVnode.elm,oldVnode.children,newVonde.children);
}
else {
// 不含有children
console.log('不含有children');
oldVnode.elm.innerHTML='';
for(let i =0; i<newVonde.children.length;i++){
let dom = createElement(newVonde[i]);
oldVnode.elm.appendChild(dom);
}
}
}
}
updateChildren.js
import createElement from "./createElement.js";
import pathVonde from "./pathVnode.js";
function checkSameVnode(a, b) {
// console.log(a,b);
return a.sel == b.sel && a.key == b.key;
}
export default function updateChildren(parentElm, oldCh, newCh) {
// 旧前
let oldStartIdx = 0;
// 新前
let newStartIdx = 0;
// 旧后
let oldEndIdx = oldCh.length - 1;
let oldEndIdx2 = oldCh.length - 1;
// 新后
let newEndIdx = newCh.length - 1;
let newEndIdx2 = newCh.length - 1;
// 旧前节点
let oldStartVnode = oldCh[0];
// 新前节点
let newStartVnode = newCh[0];
// 旧后节点
let oldEndVnode = oldCh[oldEndIdx];
// 新后节点
let newEndVnode = newCh[newEndIdx];
let keymap = null;
let i=0;
while (oldStartIdx <= oldEndIdx && newStartIdx <= newEndIdx) {
console.log('第',++i,'次循环');
if (oldStartVnode == null || oldCh[oldStartIdx] == undefined) {
console.log('break1');
oldStartVnode = oldCh[++oldStartIdx];
}
else if (oldEndVnode == null || oldCh[oldEndIdx] == undefined) {
console.log('break2');
oldEndVnode = oldCh[--oldEndIdx];
} else if (newStartVnode == null || newCh[newStartIdx] == undefined) {
console.log('break3');
newStartVnode = newCh[++newStartIdx];
}
else if (newEndVnode == null || newCh[newEndIdx] == undefined) {
console.log('break4');
newEndVnode = newCh[--newEndIdx];
}
else if (checkSameVnode(oldStartVnode, newStartVnode)) {
console.log('新前旧前',newStartIdx,oldStartIdx);
pathVonde(oldStartVnode, newStartVnode);
oldStartVnode = oldCh[++oldStartIdx];
newStartVnode = newCh[++newStartIdx];
}
else if (checkSameVnode(oldEndVnode, newEndVnode)) {
console.log('新后旧后',newEndIdx,oldEndIdx);
pathVonde(oldEndVnode, newEndVnode);
oldEndVnode = oldCh[--oldEndIdx];
newEndVnode = newCh[--newEndIdx];
}
else if (checkSameVnode(oldStartVnode, newEndVnode)) {
console.log('新后旧前',newEndIdx,oldStartIdx);
pathVonde(oldStartVnode, newEndVnode);
parentElm.insertBefore(oldStartVnode.elm, oldStartVnode.elm.nextSibling);
oldStartVnode = oldCh[++oldStartIdx];
newEndVnode = newCh[--newEndIdx];
}
else if (checkSameVnode(oldEndVnode, newStartVnode)) {
console.log('新前旧后',newStartIdx,oldEndIdx);
pathVonde(oldEndVnode, newStartVnode);
parentElm.insertBefore(oldEndVnode.elm, oldStartVnode.elm);
oldEndVnode = oldCh[--oldEndIdx];
newStartVnode = newCh[++newStartIdx];
}
else {
console.log('都不匹配');
if (!keymap) {
console.log('!keymap');
keymap = {}
for (let i = oldStartIdx; i < oldEndIdx; i++) {
const key = oldCh[i].key;
if (key != undefined) {
keymap[key] = i;
}
}
}
const idxInOld = keymap[newStartVnode.key]
if (idxInOld == undefined) {
console.log('idxInOld == undefined');
// 全新项
parentElm.insertBefore(createElement(newStartVnode),oldStartVnode.elm)
}
else {
console.log('idxInOld != undefined');
// 不是全新项,需要移动
const elmToMove = oldCh[idxInOld];
pathVonde(elmToMove, newStartVnode);
// 标记为处理过
oldCh[idxInOld] = undefined;
// 移动
parentElm.insertBefore(elmToMove.elm, oldStartVnode.elm);
}
newStartVnode = newCh[++newStartIdx]
}
}
console.log('新前:',newStartIdx,'新后:',newEndIdx);
console.log('旧前:',oldStartIdx,'旧后:',oldEndIdx);
if (newStartIdx <= newEndIdx) {
console.log('newStartIdx <= newEndIdx');
const before = newCh[newEndIdx + 1] == null ? null : newCh[newEndIdx + 1].elm;
// const before = oldCh[oldEndIdx] == null ? null : oldCh[oldEndIdx].elm;
console.log(before);
for (let i = newStartIdx; i <=newEndIdx ; i++) {
parentElm.insertBefore(createElement(newCh[i]), before);
}
}
else if (oldStartIdx <= oldEndIdx) {
console.log('oldStartIdx <= oldEndIdx');
// 批量删除
for (let i = oldStartIdx; i <= oldEndIdx; i++) {
if (oldCh[i]) {
parentElm.removeChild(oldCh[i].elm)
}
}
}
}
index.js
// import {
// init,
// classModule,
// propsModule,
// styleModule,
// eventListenersModule,
// h,
// } from "snabbdom";
// const patch = init([
// // Init patch function with chosen modules
// classModule, // makes it easy to toggle classes
// propsModule, // for setting properties on DOM elements
// styleModule, // handles styling on elements with support for animations
// eventListenersModule, // attaches event listeners
// ]);
import patch from './mysnabbdom/patch.js';
import h from './mysnabbdom/h.js';
const container=document.getElementById('container')
// document.getElementById
// document.getElementsByClassName
// document.getElementsByName
// document.getElementsByTagName
// document.getElementsByTagNameNS
// const myVonde1=h('h1',{},'hello')
// const myVonde1 = h('ul', {}, [
// h('li', { key: 'A' }, 'A'),
// h('li', { key: 'B' }, 'B'),
// h('li', { key: 'C' }, 'C'),
// h('li', { key: 'D' }, 'D'),
// h('li', { key: 'E' }, 'E'),
// ])
// patch(container, myVonde1);
// const vnode2 = h('ul', {}, [
// h('li', { key: 'Q' }, 'Q'),
// h('li', { key: 'T' }, 'T'),
// h('li', { key: 'E' }, 'E'),
// h('li', { key: 'B' }, 'B'),
// h('li', { key: 'A' }, 'A'),
// h('li', { key: 'D' }, 'D'),
// h('li', { key: 'C' }, 'C'),
// h('li', { key: 'V' }, 'V'),
// h('li', { key: 'Z' }, 'Z'),
// ])
// const myVonde1 = h('ul', {}, [
// h('li', { key: 'A' }, 'A'),
// h('li', { key: 'B' }, 'B'),
// h('li', { key: 'C' }, 'C'),
// h('li', { key: 'D' }, 'D')
// ])
// patch(container, myVonde1);
// const vnode2 = h('ul', {}, [
// h('li', { key: 'E' }, 'E'),
// h('li', { key: 'A' }, 'A'),
// h('li', { key: 'B' }, 'B'),
// h('li', { key: 'E' }, 'E'),
// h('li', { key: 'C' }, 'C'),
// h('li', { key: 'D' }, 'D'),
// h('li', { key: 'E' }, 'E'),
// ])
btn.onclick = function () {
patch(myVonde1, vnode2);
}