这可能是一篇很难理解的文章,我想了许久,但我不想废话
我们经常听说 react 的 diff 算法是 O(n),说的其实是最好情况
diff (oldVnode, newVnode){
if (oldVnode.type !== newVnode.type){
// 命中这里,则替换节点,放弃子树遍历,全部命中这里,则为 O(n)
}else{
// 命中这里,则遍历字节点,则为 O(n^2)
}
}
但是链表不一样,它除了做 diff,还要做链表生成的逻辑,children 是一定要遍历的
while (true) {
// 生成链表
for (var i = 0, prev; i < children.length; i++) {
const fiber = children[i]
fiber.parent = wip
if (i > 0) {
prev.sibling = fiber
} else {
parent.child = fiber
}
prev = fiber
}
// 遍历链表
if (fiber === root) {
break
}
while (!fiber.sibling) {
if (!fiber.return || fiber.return === root) {
return;
}
fiber = fiber.return
}
fiber = fiber.sibling
}
上面是整个链表的遍历,必然的 O(n^2)
我们要想实现 type 的优化,唯一的办法只有跳出主循环,而跳出主循环的决定性因素在于链表生成的部分
for (var i = 0, prev, oldFiber = wip.alternate; i < children.length; i++) {
const fiber = children[i]
fiber.parent = parent
if (oldFiber) {
fiber.alternate = oldFiber
oldFiber = oldFiber.sibling
}
if (i > 0) {
prev.sibling = fiber
} else {
parent.child = fiber
}
prev = fiber
}
如上,我们使用双缓冲制造了一个 oldFiber
for (var i = 0, prev, oldFiber = wip.alternate; i < children.length; i++) {
const fiber = children[i]
if (oldFiber) {
if(oldFiber.type !== fiber.type){
continue // 放弃链表生成
}else{
oldFiber = oldFiber.sibling
fiber.alternate = oldFiber
}
}
fiber.parent = wip
if (i > 0) {
prev.sibling = fiber
} else {
parent.child = fiber
}
prev = fiber
}
就这样,我们对这个循环,增加了根据 type 放弃子树渲染的优化可能
然后我们就可以在这个链表上尝试增加常规算法了
但是这还没完,实际想要真正使用递归算法,仍旧非常困难,但是无论如何
链表也是可以做到对等的 diff 算法的
react 应该是没有这个优化的
fre1 没有这种根据 type 打断子树的优化,是因为抄 react 的时候没有发现……
这个优化实际上只是打断子树,dom 操作还是不会少的
if(oldFiber.type !== fiber.type){
const op = () => createElment(fiber) // 这里需要递归操作 dom
commit.push(op)
continue // 放弃链表生成
}
react 之所以没有这个优化,我猜是因为它不想递归操作 dom
因为实际上 react 的 commitWork 遍历的仍旧是链表,所以它必须保证子链的生成是完整的,因为还有 ref,effects 啥的甚至一堆我们不知道的东西要处理
fre 没有这么多东西,链表的作用只是用来实现时间切片的而已
而且 dom 操作也是同步的……根本不需要使用链表
总结
总的来说就是,生成一颗完整的链表,比如需要遍历每一个节点的子树,那么复杂度必然是 O(n^2)
我么要想搞 O(n) 的情况,必然要放弃子树,放弃它就不能生成它,所以我们只生成一颗残破的树
我把它成为【残破链表】,以后大家用到类似的优化,一定要记着我!