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

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

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

  • compile view to vnode
  • diff vnode 的变化
  • patch vnode 变化到实际的DOM

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

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


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

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

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

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

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

额~,像以下这样吗?

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

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

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

额~,像以下这样吗?

let oldVnode = {a: {a1: 1}, b: {b1: 1}}
let newVnode = Object.assign({}, oldVnode)
oldVnode === newVnode // false
newVnode.a.a1 = 2
oldVnode.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 = 2
oldVnode === newVnode // false
oldVnode.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);
复制代码
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值