感谢: 蜗牛老湿的分析
Vue1.x
没有 vdom
,完全的响应式,每个数据变化,都通过响应式通知机制来新建 Watcher
干活,就像独立团规模小的时候,每个战士入伍和升职,都主动通知咱老李,管理方便
项目规模变大后,过多的 Watcher
,会导致性能的瓶颈
React15x
而 React15
时代,没有响应式,数据变了,整个新数据和老的数据做 diff
,算出差异 就知道怎么去修改 dom
了,就像老李指挥室有一个模型,每次人事变更,通过对比所有人前后差异,就知道了变化, 看起来有很多计算量,但是这种 immutable
的数据结构对大型项目比较友好,而且 Vdom
抽象成功后,换成别的平台render成为了可能,无论是打鬼子还是打国军,都用一个 vdom
模式
碰到的问题一样,如果 dom
节点持续变多,每次 diff
的时间超过了 16ms
,就可能会造成卡顿(60fps)
Vue2.x
引入vdom,控制了颗粒度,组件层面走watcher通知, 组件内部走vdom做diff,既不会有太多watcher,也不会让vdom的规模过大,diff超过16ms,真是优秀啊 就像独立团大了以后,只有营长排长级别的变动,才会通知老李,内部的自己diff管理了
React 16 Fiber
React
走了另外一条路,既然主要问题是 diff
导致卡顿,于是 React
走了类似 cpu
调度的逻辑,把 vdom
这棵树,微观变成了链表,利用浏览器的空闲时间来做 diff
,如果超过了 16ms
,有动画或者用户交互的任务,就把主进程控制权还给浏览器,等空闲了继续,特别像等待女神的备胎
diff
的逻辑,变成了单向的链表,任何时候主线程女神有空了,我们在继续蹭上去接盘做 diff
,大家研究下 requestIdleCallback
就知道,从浏览器角度看 是这样的
大概代码
requestIdelCallback(myNonEssentialWork);
// 等待女神空闲
function myNonEssentialWork (deadline)
{
// deadline.timeRemaining()>0 主线程女神还有事件
// 还有diff任务没算玩
while
(deadline.timeRemaining()
>
0
&& tasks.length >
0)
{
doWorkIfNeeded();
}
// 女神没时间了,把女神还回去
if
(tasks.length >
0){
requestIdleCallback(myNonEssentialWork);
}
}
Vue3
这里的静态提升和事件缓存刚才说过了,就不说了,其实我也很纳闷,这些静态标记和事件缓存, React
本身也可以做,为啥就不实现了,连 shouldComponentUpdate
都得自己定义,为啥不把默认的组件都变成 pure
或者 memo
呢,唉,也许这就是人生把
React
给你自由, Vue
让你持久,可能也是现在国内Vue和React都如此受欢迎的原因把
Vue3
通过 Proxy
响应式+组件内部 vdom
+静态标记,把任务颗粒度控制的足够细致,所以也不太需要 time-slice
了。
diff算法补充:
dom的更新策略不同
react 会自顶向下全diff.
vue会跟踪每一个组件的依赖关系,不需要重新渲染整个组件树。
1. 在react中,当状态发生改变时,组件树就会自顶向下的全diff, 重新render页面, 重新生成新的虚拟dom tree, 新旧dom tree进行比较, 进行patch打补丁方式,局部跟新dom. 所以react为了避免父组件跟新而引起不必要的子组件更新, 可以在shouldComponentUpdate做逻辑判断,减少没必要的render, 以及重新生成虚拟dom,做差量对比过程.
2. 在 vue中, 通过Object.defineProperty 把这些 data 属性 全部转为 getter/setter。同时watcher实例对象会在组件渲染时,将属性记录为dep, 当dep 项中的 setter被调用时,通知watch重新计算,使得关联组件更新。
Diff 算法借助元素的 Key 判断元素是新增、删除、修改,从而减少不必要的元素重渲染。
基于Diff的开发建议
1、基于tree diff:
(1)开发组件时,注意保持DOM结构的稳定;即尽可能少地动态操作DOM结构,尤其是移动操作。
(2)当节点数过大或者页面更新次数过多时,页面卡顿的现象会比较明显。这时可以通过 CSS 隐藏或显示节点,而不是真的移除或添加 DOM 节点。
2、基于component diff:
(1)注意使用 shouldComponentUpdate() 来减少组件不必要的更新。
(2)对于类似的结构应该尽量封装成组件,既减少代码量,又能减少component diff的性能消耗。
3、基于element diff:
(1)对于列表结构,尽量减少类似将最后一个节点移动到列表首部的操作,当节点数量过大或更新操作过于频繁时,在一定程度上会影响渲染性能。
(2)循环渲染的必须加上key值,唯一标识节点