前端开发的十万个为什么(二)
Rollup, Vite, Webpack 有什么联系和区别?
- 三者分别都是前端项目的打包和构建工具。
- Rollup 用于构建 JavaScript 库,目标是尽可能小和快地构建,提倡使用 ESM 进行模块化开发,热门项目主要是通用性框架:React,Vue,Vuex 和 Vue-Router。
- Webpack 通常用于复杂项目的打包,有更强的代码拆分能力,热门项目主要是组件库:Element-Plus,Mint-UI。
- Vite 是现代化的开发服务器,特点在于开发模式下不用打包操作,提供极快的模块热更新和启动速度,生产模式的打包过程基于 rollup 实现。
在打包的过程中,Webpack 和 Rollup 的具体差异体现在:
- Webpack 为了更好地兼容性注入的代码比较多,Rollup 比较干净。
- Rollup 诞生于 ESM 标准出来后,原生不支持 CJS,所以可以做到更精准地打包。
- Webpack 诞生地更早,所以社区开发地插件更加完善。
Vue 3 使用 Proxy 替换 Object.defineProperty() 的好处?
Object.defineProperty()
不能直接监听数组长度的变化和对象属性的新增或删除,Vue 2 额外提供了Vue.set
和Vue.delete
方法来触发这种情况下的视图的更新。而Proxy
原生就可以支持,不需要额外的兼容。Proxy
可以直接拦截Map
,Set
等集合的操作,Object.defineProperty
则不行。- 与
Object.defineProperty()
需要递归遍历对象的每个属性不同,Proxy
可以懒观察属性,只有在属性被访问时才将其转换为响应式。这大大减少了初始化响应式数据的时间。
Vue 和 React 的 Diff 算法有什么联系和区别?
最朴实的 diff
是 N 个节点去对比 N 个节点,复杂度是 O(N^2)
。
基于两个基本原则对 diff
过程进行优化:
- 节点几乎不会跨层级移动
- 同一层级的子节点有
id
作为唯一标识符。
React 的 diff 优化
- Tree Diff(整棵树):只对同层级的虚拟 DOM 进行对比,跨层级的操作会删除原节点,创建新节点。
- Component Diff(两个组件,例如
<MyComp />
):类型相同时,优先对比 Virtual DOM 树,其次根据shouldComponentUppubDate
来判断。 - Element Diff(同层级的两个节点,例如
div
):
- 通过
key
来遍历新旧集合,判断是否需要创建和删除 - 只在
newIndex < oldIndex
的时候才会进行移动。
为什么 element diff 的时候只能前移? 避免不同节点的前移和后移抵消;
Vue 2 的双端对比
- 初始化指针:设置 4 个指针,指向旧节点和新节点 list 的首尾。
- 循环比较:
- 首尾比较:先比较两个 首节点,然后比较两个 尾节点。如果节点相同(
key
相同),就直接更新节点,并移动指针。 - 交叉比较:如果首尾没有找到相同节点,就进行交叉比较,比较旧列表的 首节点 和新列表的 尾节点,以及旧列表的 尾节点 和新列表的 首节点。如果相同,移动并更新节点,移动指针。
- 创建新节点:如果上述都没有找到相同的节点,就在旧列表中找和新列表首节点相同的节点。如果找到了,就移动对应节点。否则就创建新的 DOM。
- 更新完节点之后,移动指针,进入下一轮。
- 添加剩余节点:如果新列表的指针没有走完,说明后剩下的都是新增的节点。
- 移除多余节点:如果旧列表的指针还没有走完,说明多余的都是待删除的多余节点。
Vue 3
Vue 3 中针对标注了 key
的元素,还是保留了旧版的双端对比操作。
除此之外,还新增了许多额外的优化:
- 在编译阶段标注
hoist
静态节点,并且将多个静态节点用innerHTML
的方式进行render
,以此减少不必要的对比和重渲染。 - 引入
Fragment
和Block
的概念,允许单个组件拥有多个根节点,追踪动态的节点。 - 编译过程中,对节点的类型进行标注,利用位运算标注
PatchFlag
,限制diff
过程中判断是否更新的操作。