1、react dom diff的实现方式
const oldArr = [
{
tag: 'A',
key: 'v-1',
mountIndex: 0,
children: [{ value: 'hello old v-1' }]
},
{
tag: 'A',
key: 'v-2',
mountIndex: 1,
children: [{ value: 'hello old v-2' }]
},
{
tag: 'A',
key: 'v-3',
mountIndex: 2,
children: [{ value: 'hello old v-3' }]
}
]
const newArr = [
{
tag: 'A',
key: 'v-3',
mountIndex: 0,
children: [{ value: 'hello new v-2' }]
},
{
tag: 'A',
key: 'v-2',
mountIndex: 1,
children: [{ value: 'hello new v-1' }]
},
{
tag: 'A',
key: 'v-4',
mountIndex: 2,
children: [{ value: 'hello new v-3' }]
}
]
// react diff 的简单实现
function updateChildren(parentDOM = {}, oldVChildren, newVChildren) {
let keyedOldMap = {}
oldVChildren.forEach((oldVChild, index) => {
let oldKey = oldVChild.key ? oldVChild.key : index
keyedOldMap[oldKey] = oldVChild
})
let patch = []
let lastPlacedIndex = 0
newVChildren.forEach((newVChild, index) => {
newVChild.mountIndex = index
let newKey = newVChild.key ? newVChild.key : index
let oldVChild = keyedOldMap[newKey]
if (oldVChild) {
//如果说明老节点找到了,可以复用老节点
//先更新
// updateElement(oldVChild, newVChild)
// 如果 mountIndex < lastPlacedIndex 说明要移动,位置已经变了
if (oldVChild.mountIndex < lastPlacedIndex) {
patch.push({
type: 'MOVE',
oldVChild, //把oldVChild移动互当前的索引处
newVChild,
mountIndex: index
})
}
delete keyedOldMap[newKey]
lastPlacedIndex = Math.max(oldVChild.mountIndex, lastPlacedIndex)
} else {
// 老的里面没有,说明要新建
patch.push({
type: 'PLACEMENT',
newVChild,
mountIndex: index
})
}
})
//获取需要移动的元素
let moveChildren = patch.filter(action => action.type === 'MOVE').map(action => action.oldVChild)
// 需要删掉的dom
const deleteChildren = Object.values(keyedOldMap).concat(moveChildren)
//遍历完成后在map留下的元素就是没有被 复用到的元素,需要全部删除
deleteChildren.forEach(oldVChild => {
// 从真实dom 里面删除
// let currentDOM = findDOM(oldVChild)
// parentDOM.removeChild(currentDOM)
})
console.log('deleteChildren: ', deleteChildren)
console.log('patch: ', patch)
// 更新 真实dom
patch.forEach(action => {
let { type, oldVChild, newVChild, mountIndex } = action
let childNodes = parentDOM.childNodes
// if (type === PLACEMENT) {
// let newDOM = createDOM(newVChild) //根据新的虚拟DOM创建新的真实DOM
// let childNode = childNodes[mountIndex] //获取 原来老的DOM中的对应的索引处的真实DOM
// if (childNode) {
// parentDOM.insertBefore(newDOM, childNode)
// } else {
// parentDOM.appendChild(newDOM)
// }
// } else if (type === MOVE) {
// let oldDOM = findDOM(oldVChild)
// let childNode = childNodes[mountIndex] //获取 原来老的DOM中的对应的索引处的真实DOM
// if (childNode) {
// parentDOM.insertBefore(oldDOM, childNode)
// } else {
// parentDOM.appendChild(oldDOM)
// }
// }
})
}
2、vue3 dom diff的实现方式
const patchKeydChildren = (c1, c2, el) => {
// 内部有优化策略
// abc i = 0
// abde 从头比
let i = 0
let e1 = c1.length - 1
let e2 = c2.length - 1
// 从头开始 一个一个的比,遇到不同的就停止
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = c2[i]
if (isSameVnodeType(n1, n2)) {
patch(n1, n2, el)
} else {
break
}
i++
}
// abc // e1 = 2
//eabc // e2 = 3 // 从后 向前 一个一个的比较,遇到不同的就停止
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = c2[e2]
if (isSameVnodeType(n1, n2)) {
patch(n1, n2, el)
} else {
break
}
e1--
e2--
}
// 只考虑 元素新增和删除的情况
// abc => abcd (i=3 e1=2 e2=3 ) abc => dabc (i=0 e1=-1 e2=0 )
// 只要i 大于了 e1 表示新增属性
if (i > e1) {
// 说明有新增
if (i <= e2) {
// 表示有新增的部分
// 先根据e2 取他的下一个元素 和 数组长度进行比较
const nextPos = e2 + 1
const anchor = nextPos < c2.length ? c2[nextPos].el : null
while (i <= e2) {
patch(null, c2[i], el, anchor)
i++
}
}
// abcd abc (i=3 e1=3 e2=2)
} else if (i > e2) {
// 删除
while (i <= e1) {
hostRemove(c1[i].el)
i++
}
} else {
// 无规律的情况 diff 算法
// ab [cde] fg // s1=2 e1=4
// ab [edch] fg // s2=2 e2=5
const s1 = i
const s2 = i
// 新的索引 和 key 做成一个映射表
const keyToNewIndexMap = new Map()
for (let i = s2
const nextChild = c2[i]
keyToNewIndexMap.set(nextChild.key, i)
}
const toBePatched = e2 - s2 + 1
const newIndexToOldMapIndex = new Array(toBePatched).fill(0)
// 只是做相同属性的diff 但是位置可能还不对
for (let i = s1
const prevChild = c1[i]
// 老的去 新的里面找
let newIndex = keyToNewIndexMap.get(prevChild.key)
// 新的里面没找到
if (newIndex == undefined) {
hostRemove(prevChild.el)
} else {
// 新索引 对应老的 索引 为0 代表是新增加的元素
// 数组,是以 新的索引,为key,老的索引为 value的
newIndexToOldMapIndex[newIndex - s2] = i + 1
patch(prevChild, c2[newIndex], el)
}
}
// 最长递增子序列 [0,1] [0,1,2,3]
let increasingIndexSequence = getSequence(newIndexToOldMapIndex)
let j = increasingIndexSequence.length - 1
// 倒序
for (let i = toBePatched - 1
const nextIndex = s2 + i
const nextChild = c2[nextIndex]
let anchor = nextIndex + 1 < c2.length ? c2[nextIndex + 1].el : null
if (newIndexToOldMapIndex[i] == 0) {
// 这是一个新元素 直接创建插入到 当前元素的下一个即可
patch(null, nextChild, el, anchor)
} else {
// 根据参照物 将节点直接移动过去 所有节点都要移动 (但是有些节点可以不动)
if (j < 0 || i != increasingIndexSequence[j]) {
// 此时没有考虑不动的情况
hostInsert(nextChild.el, el, anchor)
} else {
j--
}
}
}
}
}
// O(n logn) 性能好于 O(n^2)
// [2, 3, 1, 5, 6, 8, 7, 9, 4] 2 3 5 6 7 9
// 对应索引 result
// 2 对应索引 0
// 2 3 对应索引 0 1
// 1 3 对应索引 2 1
// 1 3 5 对应索引 2 1 3
// 1 3 5 6 对应索引 2 1 3 4
// 1 3 5 6 8 对应索引 2 1 3 4 5
// 1 3 5 6 7 对应索引 2 1 3 4 6
// 1 3 5 6 7 9 对应索引 2 1 3 4 5 7
// 1 3 4 6 7 9 对应索引 2 1 8 4 6 7
function getSequence(arr) {
const len = arr.length
const result = [0]
const p = arr.slice(0)
let start
let end
for (let i = 0
const arrI = arr[i]
console.log('result:i ', i, result)
if (arrI !== 0) {
let resultLastIndex = result[result.length - 1]
// 如果当前项 大于里面的直接 放入数组中
if (arr[resultLastIndex] < arrI) {
p[i] = resultLastIndex
result.push(i)
continue
// continue终止本次循环,本次后面的代码不用执行了
//break用于完全结束一个循环,跳出循环体执行循环后面的语句
}
// 二分查找 找到比当前值大的哪一个
start = 0
end = result.length - 1
while (start < end) {
middle = ((start + end) / 2) | 0
if (arr[result[middle]] < arrI) {
start = middle + 1
} else {
end = middle
}
}
// start /end 就是找的位置
// 如果相同 或者 比当前的还大 就不换了
if (arrI < arr[result[start]]) {
// 才需要替换
if (start > 0) {
p[i] = result[start - 1]
}
result[start] = i
}
}
console.log('p: ', p)
}
let len1 = result.length
let last = result[len1 - 1]
// 根据前驱节点 一个个向前查找
while (len1-- > 0) {
result[len1] = last
last = p[last]
}
return result
}
// console.log('result: ', getSequence([1, 8, 5, 3, 4, 9, 7, 5, 0]))
console.log('result:all: ', getSequence([2, 3, 1, 5, 6, 8, 7, 9, 4]))
3、Axios 中的取消请求源码解析
function source(){
let cancel;
const promise = new Promise((resolve)=>{
cancel = resolve
})
return {
cancel:cancel,
token:promise
}
}
function axios_get(config){
if(config.cancelToken){
config.cancelToken.then(()=>{
console.log("xhr.abort")
})
}
}
const source1 = source()
axios_get({cancelToken:source1.token})
setTimeout(() => {
source1.cancel()
}, 2000);