【浅显易懂】虚拟DOM,如何更高效DIFF

作者 : 开心源码 本文共2044个字,预计阅读时间需要6分钟 发布时间: 2022-05-12 共185人阅读

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 = 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 } }}

image.png

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

image.png

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

image.png

当 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);

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 【浅显易懂】虚拟DOM,如何更高效DIFF

发表回复