写点什么

【通俗易懂】虚拟 DOM,如何更高效 DIFF

用户头像
Daniel
关注
发布于: 2021 年 06 月 13 日
【通俗易懂】虚拟DOM,如何更高效DIFF

我们都知道,通过虚拟 DOM,可以减少 DOM 操作,提高页面渲染性能


要实现虚拟 DOM,主要三部曲:


  • compile view to vnode

  • diff vnode 的变化

  • patch vnode 变化到实际的 DOM


假想的黑粉:"所以这篇文章是要深入虚拟 DOM 的实现原理和实现细节吗?"


非也非也,我们开始愉快地进入正题吧


三部曲中,diff 的性能很关键,所以一般对 vnode 的 type key 作比较,如果不一致,则该 vnode 及以下孩子们全部干掉(好残忍,无法直视(>﹏<)),用新的直接替换,不再往下对比。


假想的黑粉:“这个大家都懂,所以文章至此可以结束了?”


还没。。。还没开始(# ̄▽ ̄#)


diff,那就得有 diff 的两个 vnode,一个 old vnode,一个 new vnode,那 new vnode 如何产生的呢?


假想的黑粉:“简单啊,把 old vnode 赋值给 new vnode”


额~,像以下这样吗?


let oldVnode = {a: {a1: 1}, b: {b1: 1}}let newVnode = oldVnodenewVnode === oldVnode // true
复制代码


可以看到新旧一致,无论如何赋值都是同个对象,无从对比啦


假想的黑粉:“我是想说 clone 一个啦,shadow 就行了” <( ̄︶ ̄)>


额~,像以下这样吗?


let oldVnode = {a: {a1: 1}, b: {b1: 1}}let newVnode = Object.assign({}, oldVnode)oldVnode === newVnode // falsenewVnode.a.a1 = 2oldVnode.a.a1 // 2
复制代码


可以看到更改了 new vnode 的 a1 值,old vnode 的 a1 值也被改了,也就无法得知变化了


假想的黑粉:“刚为了性能考虑说了 shadow copy,那实在不行就 deep copy 吧”


额~,像以下这样吗?


const _ = require('lodash');
let oldVnode = {a: {a1: 1}, b: {b1: 1}}let newVnode = _.cloneDeep(oldVnode)newVnode.a.a1 = 2oldVnode === newVnode // falseoldVnode.a.a1 === newVnode.a.a1 // false
复制代码


看上去没什么问题,功能是可以实现了,但这篇文章是要讲 高效 diff,上面方案有两个较不好的性能问题:


  1. deep copy 造成资源浪费,没更新的结点也被复制了一份

  2. 每次要遍历所有 vnode 进行对比,无论该 vnode 有没产生变化


假想的黑粉:“看样子你是有更好的方案,有什么花招赶紧使出来吧~”


那就让我慢慢道来,先来个中横线分割一下先( ̄︶ ̄)↗


只要避免上面提到的两点对性能的影响,即可更高效 DIFF,对应的措施如下:


  1. 按需 copy:没出现变化的 vnode 不作 copy

  2. 按需 diff:没出现变化的 vnode 不作 diff


假设 vnode 的数据结构以及图形表示如下:


let oldVnode = {    a: {        a1: { a1_1: 1 },        a2: { a2_1: 1 }    },    b: { b1: { b1_1: 1 } }}
复制代码



当把 a1_1 的值更改为 2 时, 我们希望只对以下高亮节点进行 shadow copy 或赋值,以下即为 new vnode



所以在对比 old vnode 和 new vnode 时,只有下图高亮的节点需要进行比对



当 a2 和 b 节点下面的子节点越多时,copy 和 diff 所带来的性能收益就越明显


最后献上这种方案的极简单极粗糙的实现(update 方法,只考虑对象,没考虑数组)以更好的从代码层面去理解这种思路


const assert = require('assert');
let oldVal = {a: {a1: 1}, b: {b1: 2}}
function update(obj, path, val) { let fileds = path.split('.'); let shadowCopy = targetObj => Object.assign({}, targetObj); let result = shadowCopy(obj); if (fileds.length > 0) { if (fileds.length === 1) { result[fileds[0]] = val; } else { result[fileds[0]] = update(obj[fileds[0]], fileds.length > 1 ? fileds.splice(1).join('.') : '', val) } } return result;}
const newVal = update(oldVal, 'a.a1', 2);
assert.notStrictEqual(oldVal, newVal);assert.notStrictEqual(oldVal.a, newVal.a);assert.notStrictEqual(oldVal.a.a1, newVal.a.a1);assert.strictEqual(oldVal.b, newVal.b);
复制代码


发布于: 2021 年 06 月 13 日阅读数: 84
用户头像

Daniel

关注

一源一世界 2019.03.03 加入

ncform / ncgen / nice-hooks 开源项目作者

评论

发布
暂无评论
【通俗易懂】虚拟DOM,如何更高效DIFF