1.简单list diff
var move = function(list, from, to) {
var item = list.splice(from, 1);
if (from > to)
list.splice(to, 0, item[0]);
else
list.splice(to - 1, 0, item[0]);
};
var diff = function(oldList, newList) {
var changes = [];
// 镜像,模拟位置
var _oldList = oldList.slice();
// 遍历旧的,找出 删
oldList.forEach(function(item, i) {
if (newList.indexOf(item) === -1) {
changes.push({
type: 'remove',
index: i
});
_oldList.splice(i, 1);
}
});
// 遍历新的,找出 增/移/改
newList.forEach(function(item, i) {
var index = _oldList.indexOf(item);
if (index === -1) {
// 增
changes.push({
type: 'insert',
index: i,
item: item
});
_oldList.splice(i, 0, item);
}
else {
// 移
if (index !== i) {
var step = {
type: 'move',
from: index,
to: i
};
changes.push(step);
move(_oldList, step.from, step.to);
}
}
});
return changes;
};
// test
var showSteps = function(changes) {
changes.forEach(function(change) {
switch (change.type) {
case 'insert':
console.log('insert ' + change.item + ' before ' + oldList[change.index]);
oldList.splice(change.index, 0, change.item);
break;
case 'remove':
console.log('remove ' + oldList[change.index]);
oldList.splice(change.index, 1);
break;
case 'check':
console.log('check ' + oldList[change.index] + ' for update');
break;
case 'move':
console.log('move ' + oldList[change.from] + ' to ' + oldList[change.to]);
move(oldList, change.from, change.to);
break;
default:
cosole.error('not here');
break;
}
console.log(oldList);
});
};
var oldList = [1, 2, 3, 7, 4];
var newList = [1, 4, 5, 3, 7, 6];
var changes = diff(oldList, newList);
showSteps(changes);
2.优化空间消耗
// 去掉模拟位置的东西,直接计算changes
var diff = function(oldList, newList) {
var changes = [];
// 计算change对oldList未比较元素index的影响,是个区间
// 因为移动我前面的元素到更前面,并不影响我的index
// move时,只影响<=lastIndex的index
// 用<=index: delta表示
var maxLength = Math.max(oldList.length, newList.length);
var oldIndexChanges = new Array(maxLength).fill(0);
// 修正前的index
var _index;
// 遍历新的,找出 增/移
newList.forEach(function(item, i) {
var index = oldList.indexOf(item);
if (index === -1) {
// 增
changes.push({
type: 'insert',
index: i,
item: item
});
// insert影响oldList中后面所有元素的index
oldIndexChanges[maxLength - 1]++;
}
else {
_index = index;
// 修正old index
// 从index数到头,求sum delta
index += oldIndexChanges.reduce(function(acc, delta, idx) {
if (idx >= index)
return acc + delta;
else
return acc;
});
// 移
if (index !== i) {
var step = {
type: 'move',
from: index,
to: i
};
changes.push(step);
if (index > i) {
// move影响oldList中<=from的元素
oldIndexChanges[_index]++;
}
else {
// from 不可能小于 to
// 因为是从前往后扫过来的,[0, to-1]位置确定,不会从前面取元素
console.error('impossible');
}
}
}
});
// 遍历旧的,找出 删
// 计算总delta
// 经过insert和move之后,将被删除的元素一定在最后面,受所有index change影响
var indexDelta = oldIndexChanges.reduce(function(acc, delta) {
return acc + delta;
});
oldList.forEach(function(item, i) {
if (newList.indexOf(item) === -1) {
// 修正old index
i += indexDelta;
changes.push({
type: 'remove',
index: i
});
}
});
return changes;
};
// test
var move = function(list, from, to) {
var item = list.splice(from, 1);
if (from > to)
list.splice(to, 0, item[0]);
else
list.splice(to - 1, 0, item[0]);
};
var showSteps = function(changes) {
changes.forEach(function(change) {
switch (change.type) {
case 'insert':
console.log('insert ' + change.item + ' before ' + oldList[change.index]);
oldList.splice(change.index, 0, change.item);
break;
case 'remove':
console.log('remove ' + oldList[change.index]);
oldList.splice(change.index, 1);
break;
case 'check':
console.log('check ' + oldList[change.index] + ' for update');
break;
case 'move':
console.log('move ' + oldList[change.from] + ' to ' + oldList[change.to]);
move(oldList, change.from, change.to);
break;
default:
cosole.error('not here');
break;
}
console.log(oldList);
});
};
var oldList = [1, 2, 3, 7, 4];
var newList = [1, 4, 5, 3, 7, 6];
// var oldList = [1, 3, 7, 8];
// var newList = [8, 3, 7, 1];
var changes = diff(oldList, newList);
showSteps(changes);
3.react list diff
// react原版实现
var diff = function(oldList, newList) {
var changes = [];
// 访问过的之前children表最大的index
var lastIndex = 0;
// 上一个放好的节点,用来做 增/移
var lastPlacedNode = null;
// 遍历新的,找出 增/移
newList.forEach(function(item, i) {
var index = oldList.indexOf(item);
if (index === -1) {
// 增
changes.push({
type: 'insert',
item: item,
afterNode: lastPlacedNode
});
}
else {
// lastIndex滤掉相对位置没变的 移
if (index < lastIndex) {
// 移
var step = {
type: 'move',
item: item,
afterNode: lastPlacedNode
};
changes.push(step);
}
lastIndex = Math.max(index, lastIndex);
}
lastPlacedNode = item;
});
// 遍历旧的,找出 删
oldList.forEach(function(item, i) {
if (newList.indexOf(item) === -1) {
changes.push({
type: 'remove',
index: i
});
}
});
return changes;
};
// test
var move = function(list, from, to) {
var item = list.splice(from, 1);
if (from > to)
list.splice(to + 1, 0, item[0]);
else
list.splice(to, 0, item[0]);
};
var insertAfter = function(list, item, index) {
list.splice(index + 1, 0, item);
};
var showSteps = function(changes) {
// 留一份,针对 移 用来查以前的位置
var _oldList = oldList.slice();
// 针对 增 移 和 删,模拟DOM操作需要知道patching中,旧元素的当前index
// 实际做DOM patch的时候不需要,因为找到元素后DOM API不需要index就能 增 移 删
var patchingIndex = -1;
changes.forEach(function(change) {
switch (change.type) {
case 'insert':
console.log('insert ' + change.item + ' after ' + change.afterNode);
patchingIndex = oldList.indexOf(change.afterNode);
insertAfter(oldList, change.item, patchingIndex);
break;
case 'remove':
console.log('remove ' + _oldList[change.index]);
patchingIndex = oldList.indexOf(_oldList[change.index]);
oldList.splice(patchingIndex, 1);
break;
case 'move':
console.log('move ' + change.item + ' to pos after ' + change.afterNode);
patchingIndex = oldList.indexOf(change.afterNode);
move(oldList, oldList.indexOf(change.item), patchingIndex);
break;
default:
cosole.error('not here');
break;
}
console.log(oldList);
});
};
var oldList = [1, 2, 3, 7, 4];
var newList = [1, 4, 5, 3, 7, 6];
// var oldList = [1, 3, 7, 8];
// var newList = [8, 3, 7, 1];
var changes = diff(oldList, newList);
showSteps(changes);