美文网首页
手写虚拟DOM(三)—— Diff算法优化

手写虚拟DOM(三)—— Diff算法优化

作者: 青叶小小 | 来源:发表于2020-12-26 13:30 被阅读0次

本文为系列文章:

手写虚拟DOM(一)—— VirtualDOM介绍
手写虚拟DOM(二)—— VirtualDOM Diff
手写虚拟DOM(三)—— Diff算法优化
手写虚拟DOM(四)—— 进一步提升Diff效率之关键字Key
手写虚拟DOM(五)—— 自定义组件
手写虚拟DOM(六)—— 事件处理
手写虚拟DOM(七)—— 异步更新

一、前言

本文继续上一节的Virtual DOM Diff,来聊一聊,有哪些可优化的点:

  • 我们通过diff获取了新、老 Virtual DOM的差异,然后再对现有DOM进行打补丁;
    既然,我们都找出了差异,何不直接修改DOM,还要再多此一举呢?
  • 我们每次都会保存上一次的Virtual DOM,与新的Virtual DOM比较,再转成真实的dom树;
    那我们何不将Virtual DOM与真实dom树直接关联,diff差异,然后直接更新呢?

二、优化

2.1、优化diff,去掉patch

// 改造 render
function render(container) {
    const vdom = view();
    if (!vdomPre) {
        container.appendChild(createElement(vdom));
    } else {
        diff(vdomPre, vdom, container);  <====== 直接传入 root container
    }
    vdomPre = vdom;

    setTimeout(() => {
        state.number += 1;
        render(container);
    }, 3000);
}

// 改造 diff
function diff(pre, post, parent, cid = 0) {
    const len = parent.childNodes.length;
    const child = cid >= len ? undefined : parent.childNodes[cid];

    // 原vdom没有,新vdom有,则表明是新增节点
    if (pre === undefined) {
        parent.appendChild(createElement(post));
        return;
    }

    // 原vdom有,新vdom没有,则表明是移除节点
    if (post === undefined) {
        parent.removeChild(child);
        return
    }

    // 新老节点(类型不同 or tag不同 or 内容不同),则表明是替换节点
    if (typeof pre !== typeof post || pre.tag !== post.tag ||
        (typeof pre === 'string' || typeof pre === 'number') && pre !== post) {
        parent.replaceChild(createElement(post), child);
        return;
    }

    // 至此,只有可能是当前vdom的自身props变化 or 其children发生变化
    if (pre.tag) {
        diffProps(pre.props, post.props, child);
        diffChildren(pre, post, child);
    }
}

// 改造 diffProps
function diffProps(preProps, postProps, element) {
    // 合并所有props的键值(后者替换前者)
    const all = {...preProps, ...postProps};

    // 遍历props的所有键值
    Object.keys(all).forEach(key => {
        const ov = preProps[key];
        const nv = postProps[key];

        // 新vdom没有该属性
        if (nv === undefined) {
            element.removeAttribute(key);
        }

        // 老vdom没有该属性,or 该属性值与新vdom的属性值不一致
        if (ov === undefined || ov !== nv) {
            element.setAttribute(key, nv);
        }
    });
}

// 改造 diffChildren
function diffChildren(pre, post, element) {
    // 子元素最大长度
    const len = Math.max(pre.children.length, post.children.length);

    // 依次遍历并diff子元素
    for (let i = 0; i < len; i ++) {
        diff(pre.children[i], post.children[i], element, i);
    }
}

在diff、diffProps中,直接操作dom或者属性。

2.2、关联真实dom树,直接更新

// 修改 render
function render(container) {
    diff(view(), container);

    setTimeout(() => {
        state.number += 1;
        render(container);
    }, 3000);
}

// 修改 setProps
function setProps(element, props) {
    for (let k in props) {
        if (props.hasOwnProperty(k)) {
            element.setAttribute(k, props[k]);
        }
    }

    // 保存当前的属性,之后用于新VirtualDOM的属性比较
    element['props'] = props;    <===== 重点是这里
}

// 修改 diff
function diff(vdom, parent, cid = 0) {
    const len = parent.childNodes.length;

    // child是当前真实dom!
    const child = cid >= len ? undefined : parent.childNodes[cid];

    // 原dom没有,新vdom有,则表明是新增节点
    if (child === undefined) {
        parent.appendChild(createElement(vdom));
        return;
    }

    // 原dom有,新vdom没有,则表明是移除节点
    if (vdom === undefined) {
        parent.removeChild(child);
        return
    }

    // 新老节点(类型不同 or tag不同 or 内容不同),则表明是替换节点
    if (!isEqual(vdom, child)) {
        parent.replaceChild(createElement(vdom), child);
        return;
    }

    // 至此,只有可能是当前vdom的自身props变化 or 其children发生变化
    if (child.nodeType === Node.ELEMENT_NODE) {
        diffProps(vdom.props, child);
        diffChildren(vdom, child);
    }
}


这里,用到了一个新的比较方法判断是否需要Replace节点:
function isEqual(vdom, element) {
    const elType = element.nodeType;
    const vdomType = typeof vdom;

    // 检查dom元素是文本节点的情况
    if (elType === Node.TEXT_NODE &&
        (vdomType === 'string' || vdomType === 'number') &&
        element.nodeValue === vdom) {
        return true;
    }

    // 检查dom元素是普通节点的情况
    if (elType === Node.ELEMENT_NODE && element.tagName.toLowerCase() === vdom.tag.toLowerCase()) {
        return true;
    }

    return false;
}

// 修改 diffProps
function diffProps(props, element) {
    // 合并所有props的键值(后者替换前者)
    const all = {...element['props'], ...props};   <===== 合并直接dom中的props与新的VirtualDOM的props
    const newProps = {};

    // 遍历props的所有键值
    Object.keys(all).forEach(key => {
        const ov = element['props'][key];
        const nv = props[key];

        // 新vdom没有该属性
        if (nv === undefined) {
            element.removeAttribute(key);
            return;
        }

        // 老vdom没有该属性,or 该属性值与新vdom的属性值不一致
        if (ov === undefined || ov !== nv) {
            element.setAttribute(key, nv);
        }
        newProps[key] = all[key];
    });
    element['props'] = newProps;  // 保存最新的属性
}

// 修改 diffChildren
function diffChildren(vdom, element) {
    // 子元素最大长度
    const len = Math.max(element.childNodes.length, vdom.children.length);

    // 依次遍历并diff子元素
    for (let i = 0; i < len; i ++) {
        diff(vdom.children[i], element, i);
    }
}

三、总结

本文基于上一个版本的代码,简化了页面渲染的过程(省略patch对象),
同时提供了更灵活的VD比较方法(直接跟dom比较),可用性越来越强了。

项目源码:
https://github.com/qingye/VirtualDOM-Study/tree/master/VirtualDOM-Study-03

相关文章

  • 手写虚拟DOM(三)—— Diff算法优化

    本文为系列文章: 手写虚拟DOM(一)—— VirtualDOM介绍[https://www.jianshu.co...

  • 虚拟dom和diff算法

    虚拟DOM和diff算法 diff:精细化比对最小量更新 真实DOM和虚拟DOM 虚拟DOM:用JavaScrip...

  • react VS Vue diff算法

    react diff diff算法的作用:数据更改,生成相应的虚拟DOM,与真实DOM作对比,通过diff算法,对...

  • 第十七天

    1.你怎么理解vue中的diff算法? diff算法是虚拟DOM技术的必然产物:通过新旧虚拟DOM作对比(即dif...

  • Diff 算法、key

    概念 DOM diff 就是对比两棵虚拟 DOM 树的算法。当组件变化时,会得到一个新的虚拟 DOM,diff 算...

  • 深入理解react中的虚拟DOM、diff算法

    文章结构: React中的虚拟DOM是什么? 虚拟DOM的简单实现(diff算法) 虚拟DOM的内部工作原理 Re...

  • Vue3.0 的性能优化

    1.diff算法优化 vue2.0 中虚拟dom 是全量对比;vue3.0 中新增了静态标记(patchFlag)...

  • vue系列---vue-diff

    1.vue-diff 是什么? 提到vue的diff算法就不得不提一个名词 虚拟dom(Virtual DOM) ...

  • 你怎么理解vue中的diff算法?

    diff算法是虚拟 DOM 的必然产物,通过新旧虚拟 DOM 对比,将变化的地方更新在真实 DOM 上,另外也需要...

  • 理解vue2.x之diff算法

    了解diff算法前,应该先了解虚拟DOM(VNode),在vue中是先创建VNode,再通过diff算法看哪个节点...

网友评论

      本文标题:手写虚拟DOM(三)—— Diff算法优化

      本文链接:https://www.haomeiwen.com/subject/aeubnktx.html