fre 中的结构和算法

最近考研复试临近,实在很紧张,面试要提供简历和资料,时间很短,俺只能给老师们看一下 fre 了,所以单独写一篇文章

fre 是一个 react-like 框架,使用了 560 行代码复刻了 react 异步渲染,总体上就一个 fiber 结构和一个 reconcile 算法

fiber 结构

fiber 结构本质上是使用一个三个指针的链表来描述 dom 结构,在这个链表的基础上进行深度遍历,目的是为了做异步渲染(时间切片,suspense)

js 复制代码
let root = fiber
let node = fiber
while (true) {
  if (node.child) {
    node = node.child
    continue
  }
  if (node === root) return
  while (!node.sibling) {
    if (!node.parent || node.parent=== root) return
    node = node.parent
  }
  node = node.sibling
}

传统的 diff 算法,都是基于数组的同步 diff 算法,拿 vue3、inferno 为例,本质上是寻找一个"最长公共子序列",然后保持不动,剩下的进行移动操作,而最长公共子序列可以转换为最长递增子序列问题,进而使用二分查找将复杂度优化到O(nlogn)

聪明的人已经发现了,传统的基于 LCS/LIS 的算法,是无法用到 fiber 结构里的,甚至同样的思路都不适用于链表,因为链表是个线性结构,它的遍历限制了算法的发挥(只能从头到尾 onepass)

因为我不得不思考一个新的适用于链表的 onepass 的 reconcile 算法

在 fiber 中实现最长移动

我尝试过各种算法以后,想到一种方法,既然我们没办法在链表中寻找最长公共子序列,那么我们就寻找最长移动节点

js 复制代码
[1, 2, 3, 4, 5] => [5, 1, 2, 3, 4] // 5 从 4 到了 0,明显的最大移动距离,因为我们就只移动 5

这个思路可能不能总是寻找到最短编辑距离,但大多数时候可以保持 onepass,可以和 fiber 结构结合起来,最好复杂度甚至可以做到 O(n)

js 复制代码
function reconcile(bList, view, x) {
    let currentA = aList.head
    let currentB = bList.head
    let position = 0
    
    while (currentA || currentB) {
        if (!currentB) {
            // 删除多余节点
            removeNode(currentA)
            currentA = currentA.next
            continue
        }

        if (!currentA) {
            // 插入新节点
            insertNode(currentB, null)
            currentB = currentB.next
            continue
        }

        const bKey = currentB.key
        const aKey = currentA.key

        if (aKey === bKey) {
            // 更新节点
            updateNode(currentA, currentB, view)
            currentA = currentA.next
            currentB = currentB.next
        } else {
            // 寻找最长距离
            let foundA = this.findKeyInRemaining(aList, currentA.next, bKey)
            let foundB = this.findKeyInRemaining(bList, currentB.next, aKey)

            if (foundA && foundB) {
                // 比较移动距离
                if (foundA.distance <= foundB.distance) {
                    // 移动A链表节点
                    moveNodeBefore(foundA.node, currentA)
                    currentA = foundA.node;
                } else {
                    // 移动B链表节点
                    insertNode(foundB.node, currentA)
                    currentB = foundB.node.next
                }
            } else if (foundA) {
                // 移动A链表节点
                moveNodeBefore(foundA.node, currentA);
                currentA = foundA.node
            } else {
                // 新增B链表节点
                this.insertNode(currentB, currentA)
                currentB = currentB.next
            }
        }
        position++;
    }
}

总结

以上,实际算法实现要复杂一些,仅供参考,工作也好,面试也好,学术研究也好,希望俺的思路也能帮到大家,如果大家有更好的思路也可以发俺哈

相关推荐
HHONGQI1238 分钟前
Linux 基础入门操作 第十二章 TINY Web 服务器
linux·服务器·前端
quo-te14 分钟前
【JavaWeb学习Day27】
前端·vue.js·elementui
JosieBook28 分钟前
【前端】使用 HTML、CSS 和 JavaScript 创建一个数字时钟和搜索功能的网页
前端·css·html
知识分享小能手36 分钟前
CSS3学习教程,从入门到精通,CSS3 元素的浮动与定位语法知识点及案例代码(18)
前端·javascript·css·学习·html·css3·html5
Epicurus1 小时前
Webpack实现原理
前端
Epicurus1 小时前
Webpack Loader与Plugin原理
前端
补三补四1 小时前
抓包软件【Fiddler】
前端·测试工具·fiddler
Violet5151 小时前
【JS基础】✨细说apply、call、bind:改变this指向的行为艺术📝
前端·javascript
前端加油站1 小时前
9个高级前端必会的性能优化API你都知道几个?
前端·性能优化
yaoganjili1 小时前
端智能来袭!前端工程师的GPU"偷电"指南
前端