框架源码
Vue
-
ownKeys 中的追踪是怎么设计的
ownKeys 拦截的是 Reflect.ownKeys, for…in, Object.keys 等这类遍历属性键的操作,如果有如下代码
1
2
3
4
5
6const state = reactive({ a: 1, b: 2 })
effect(() => {
for (const key in state) {
console.log(key)
}
})此时副作用依赖的是这个“对象的结构”,而不是某个属性值。应对这个场景 Vue 设计了 ITERATE_KEY 来代表对象的结构,当对象的 key 发生变化的时候就会触发 ITERATE_KEY 的 effect。同理在遍历数组键的时候实际关心的是数组的长度,所以只对 length 进行追踪
-
响应式原理
- ref处理原始值
- 对象
-
computed
- 既是effect也是dep
- 懒执行:初次访问时才计算 refreshComputed
-
watch
- getter执行的时候收集依赖(effet.run())
- 下一个微任务中执行job
- schedule异步调度,不会多次调用
-
nextTick实现
- .then
-
diff算法
- type和key相同认为是同一节点
- 双端比较+最长子序列
- 如果有key的话会通过keyToNewIndexMap索引o1的查找
- vue2双层循环查找,时间复杂度高了一个量级
- 头部对比,尾部对比,新增,删除,乱序查找最长子序列,从后往前移动
React
-
React.memo
,useMemo
和useCallback
的作用React.memo
: 缓存组件,可以传入一个判断函数来控制组件是否在父组件重新渲染时渲染。但 React 仍可能会重新渲染它:记忆化是一种性能优化,而非保证。useMemo
: 缓存计算值useCallback
: 缓存函数
-
fiber
-
fiber是什么
从运行机制上来说,fiber是一种流程让出机制,它能够让react的同步渲染中断,将控制权让回浏览器,从而达到不阻塞浏览器的目的
从数据角度上来说,fiber能够细化成一种数据结构,或者一个执行单元,它是一种增强版的vdom
从这两个方面来说,react会在跑完一个执行单元的任务之后检测还剩多少时间,如果还有时间就会继续运行,反之就终止任务将控制权转回浏览器,直到下次浏览器自身工作做完有了空闲时间,再将控制权转回react。
-
requestIdleCallback 和 requestAnimationFrame
在浏览器空闲时运行,其中callback会接收一个IdleDeadline参数,上面的timeRemaining会返回剩余时间,一般来说剩余时间是1帧耗时-这一帧浏览器渲染的所用耗时,但是在面对长任务的情况下,浏览器会稍微提高一帧耗时到50ms来应对,这样不会导致用户感觉过于卡顿,又能尽量完成任务。
react实现上采用了requestAnimationframe和messageChannel来模拟requestIdleCallback
-
为什么 fiber 用链表组织? fiber通过这种链表网络来模拟传统的js调用栈,传统的js调用栈一旦开始就不能停止,而链表的好处就在于通过提前设置next指针来还原中断之后的节点单元,fiber与fiber之间又存在兄弟父子的关系,可以很自然的形成上下文
-
在构建wip的过程中进行diff对比,
判断是否存在旧有的
fiber
节点,如果不存在说明没必要diff
,直接走fiber
新建挂载逻辑。有
child
说明有旧有fiber
,那就对比key
,如果不相等,直接运行deleteChild(returnFiber, child)
,也就是从div
节点的旧有父节点上,将整个div
都删除掉,div
的子节点都不需要比了,这也验证了react
的逐级比较,父不同,子一律都不比较视为不同。若
key
相同,那就比较新旧fiber
的type
(标签类型),如果type
不相同,跟key
不相同一样,调用了deleteRemainingChildren(returnFiber, child)
方法,直接从div
的旧有父节点上将自己整个删除。若
key type
都相同,那只能说明是props
变了,因此调用var _existing3 = useFiber(child, element.props)
方法,根据新的props
来更新旧有的div fiber
节点。
-
-
面试官:能说说什么是React的Virtual DOM吗?
Virtual DOM 是一个用 JavaScript 对象来模拟真实 DOM 的轻量级数据结构。它的核心目的是解决频繁操作真实 DOM 带来的性能问题。因为直接操作真实 DOM 会引发耗时的浏览器重排和重绘,导致页面卡顿。
Virtual DOM 提供了一个抽象层。当组件状态更新时,框架会生成一个新的 Virtual DOM 树,并与旧树进行高效的 Diff 算法比较,找出需要修改的最小差异。然后,这些差异会被一次性批量应用到真实的 DOM 上。
这个过程就像是,不是每次都去‘修补’真实的 DOM,而是先在内存中‘打好草稿’,然后一次性完成最终的修改,大大减少了 DOM 操作的次数,从而提升了应用的性能。
-
面试官:为什么要用Virtual DOM呢?直接操作DOM不行吗?
我: 主要是性能考虑。直接操作DOM是很昂贵的操作,特别是频繁的DOM操作会导致页面重排重绘。Virtual DOM的优势在于:首先,它可以批量更新。比如我在一个函数里连续调用几次setState,React会把这些更新合并成一次DOM操作,而不是每次setState都去更新DOM。其次,它有精确的diff算法。React会比较新旧Virtual DOM树,找出真正发生变化的部分,只更新那些需要更新的DOM节点。
-
面试官:能具体说说这个diff算法是怎么工作的吗?
React的diff算法基于三个假设来优化性能:
- 第一个是同层比较。React只会比较同一层级的节点,不会跨层级比较,这样复杂度从O(n³)降到O(n)。
- 第二个是组件类型比较。如果组件类型不同,React会直接销毁旧组件,创建新组件,不会深入比较。
- 第三个就是key的作用。通过key,React可以快速识别哪些元素是新增的、删除的或者移动的。这就是为什么我们在渲染列表时总是被要求加key的原因。
-
React 19有什么新的改进吗?
React 19在Virtual DOM这块有几个重要改进。最主要的是自动批处理的优化,现在不管是在事件处理函数里,还是在异步操作中,多个状态更新都会被自动批处理。另外就是并发特性的完善,通过Fiber架构,React可以中断和恢复渲染过程,让高优先级的更新先执行,提升用户体验。
-
react fiber 工作流程
当一个状态更新(比如
setState
)被触发时,双缓冲模式就开始工作了:- 后台构建:React 从 Current 树的根节点开始,在后台构建一棵 WIP 树。这个过程是增量的、可中断的,即使被暂停,用户看到的仍然是稳定的 Current 树。
- 同步更新:当 WIP 树完全构建并准备好后,React 会进入一个名为“提交(Commit)”的阶段。在这个阶段,React 会一次性地、原子性地将 WIP 树反映到真实 DOM 上。
- 切换指针:最后,React 会将 Current 树的指针指向新构建好的 WIP 树。现在,WIP 树成了新的 Current 树,而原来的 Current 树则成了旧的树,等待下一次更新时被复用。
-
面试官:能说说useLayoutEffect的作用吗?和useEffect有什么区别?
我: useLayoutEffect和useEffect最大的区别在于执行时机。useEffect是异步执行的,在浏览器绘制完成后执行;而useLayoutEffect是同步执行的,在DOM变更后、浏览器绘制前执行。
这个差别看起来很小,但在某些场景下很关键。比如需要读取DOM尺寸或者需要同步修改DOM样式时,用useLayoutEffect可以避免页面闪烁。
-
面试官:能结合React的Fiber架构解释一下这个执行时机吗?
我: 好的,这就要从Fiber的工作流程说起了。Fiber架构把React的工作分成两个主要阶段:
Render阶段是可中断的,React会构建Fiber树,进行diff比较,标记哪些节点需要更新,这个过程可能被高优先级任务打断。
Commit阶段是不可中断的,会真正应用这些变更到DOM上。Commit阶段又细分为三个子阶段:
-
Before Mutation阶段:执行getSnapshotBeforeUpdate
-
Mutation阶段:真正操作DOM,插入、更新、删除节点
-
Layout阶段:DOM变更已完成,但浏览器还没绘制
useLayoutEffect就是在Layout阶段同步执行的,这时候DOM已经更新了,但浏览器还没有绘制到屏幕上。而useEffect是在整个Commit阶段完成后,浏览器绘制完成后才异步执行。
-
-
用法:测量dom
