create-vnode
Version:
一个简易的虚拟dom生成器和渲染器,包含diff算法
147 lines (129 loc) • 3.43 kB
JavaScript
/**
* Diff two list in O(N).
* @param {Array} oldList - Original List
* @param {Array} newList - List After certain insertions, removes, or moves
* @return {Object} - {moves: <Array>}
* - moves is a list of actions that telling how to remove and insert
*/
function diff (oldList, newList, key) {
var oldMap = makeKeyIndexAndFree(oldList, key)
var newMap = makeKeyIndexAndFree(newList, key)
var newFree = newMap.free
var oldKeyIndex = oldMap.keyIndex
var newKeyIndex = newMap.keyIndex
var moves = []
// a simulate list to manipulate
var children = []
var i = 0
var item
var itemKey
var freeIndex = 0
// fist pass to check item in old list: if it's removed or not
while (i < oldList.length) {
item = oldList[i]
itemKey = getItemKey(item, key)
if (itemKey) {
if (!newKeyIndex.hasOwnProperty(itemKey)) {
children.push(null)
} else {
var newItemIndex = newKeyIndex[itemKey]
children.push(newList[newItemIndex])
}
} else {
var freeItem = newFree[freeIndex++]
children.push(freeItem || null)
}
i++
}
var simulateList = children.slice(0)
// remove items no longer exist
i = 0
while (i < simulateList.length) {
if (simulateList[i] === null) {
remove(i)
removeSimulate(i)
} else {
i++
}
}
// i is cursor pointing to a item in new list
// j is cursor pointing to a item in simulateList
var j = i = 0
while (i < newList.length) {
item = newList[i]
itemKey = getItemKey(item, key)
var simulateItem = simulateList[j]
var simulateItemKey = getItemKey(simulateItem, key)
if (simulateItem) {
if (itemKey === simulateItemKey) {
j++
} else {
// new item, just inesrt it
if (!oldKeyIndex.hasOwnProperty(itemKey)) {
insert(i, item)
} else {
// if remove current simulateItem make item in right place
// then just remove it
var nextItemKey = getItemKey(simulateList[j + 1], key)
if (nextItemKey === itemKey) {
remove(i)
removeSimulate(j)
j++ // after removing, current j is right, just jump to next one
} else {
// else insert item
insert(i, item)
}
}
}
} else {
insert(i, item)
}
i++
}
function remove (index) {
var move = {index: index, type: 0}
moves.push(move)
}
function insert (index, item) {
var move = {index: index, item: item, type: 1}
moves.push(move)
}
function removeSimulate (index) {
simulateList.splice(index, 1)
}
return {
moves: moves,
children: children
}
}
/**
* Convert list to key-item keyIndex object.
* @param {Array} list
* @param {String|Function} key
*/
function makeKeyIndexAndFree (list, key) {
var keyIndex = {}
var free = []
for (var i = 0, len = list.length; i < len; i++) {
var item = list[i]
var itemKey = getItemKey(item, key)
if (itemKey) {
keyIndex[itemKey] = i
} else {
free.push(item)
}
}
return {
keyIndex: keyIndex,
free: free
}
}
function getItemKey (item, key) {
if (!item || !key) return void 666
return typeof key === 'string'
? item[key]
: key(item)
}
// exports.makeKeyIndexAndFree = makeKeyIndexAndFree // exports for test
// exports.diff = diff
export default diff;