11

虚拟DOM如何进化为真实DOM

 3 years ago
source link: http://developer.51cto.com/art/202101/641442.htm
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前言

Vue和React的Render函数中都涉及到了Virtual DOM的概念,Virtual DOM也是性能优化上的重要一环,同时突破了直接操作真实DOM的瓶颈,本文带着以下几个问题来阐述Virtual DOM。

1.为什么要操作虚拟 DOM?

2.什么是虚拟 DOM?

3.手把手教你实现虚拟 DOM 渲染真实 DOM

希望阅读本文之后,能够让你深入的了解虚拟 DOM并且在开发和面试中收益。

为什么要操作虚拟 DOM

为了帮助我们更好的理解为什么要操作虚拟 DOM,我们先从浏览器渲染[1]一个 HTML 文件需要做哪些事情说起:

IfUfQjQ.png!mobile

浏览器渲染机制大致可以分为以下 5 步走:

1.创建 DOM tree

2.创建 Style Rules

3.构建 Render tree

4.布局 Layout

5.绘制 Painting

我们过去使用原生JavaScript和jquery去操作真实DOM的时候,浏览器会从构建 DOM 开始从头到尾的执行一遍渲染的流程。

在一次开发中,假如产品告诉你一个需求,你需要在一次操作中更新10个DOM节点,理想状态是浏览器一次性构建完DOM树,再执行后续操作。但浏览器没这么智能,收到第一个更新 DOM 请求后,并不知道后续还有9次更新操作,因此会马上执行流程,最终执行 10 次流程。

过了一会产品经理把你叫过去和你说把需求改一下,此时你又需要操作一次 DOM 的更新,那么这个时候之前做的 10 次 DOM 操作就是白白浪费性能,浪费感情。

即使计算机硬件一直在更新迭代,但是操作DOM的代价仍旧是昂贵的,频繁操作 DOM 还是会出现页面卡顿,影响用户的体验。真实的 DOM 节点,哪怕一个最简单的 div 也包含着很多属性,可以打印出来直观感受一下:

IfymAzz.png!mobile

如此多的属性,如果每次对 DOM 结构都进行更新,一次,两次,三次...一百次....一千次...,可想而知,是多么庞大的数据量。

因此虚拟DOM就是为了解决这个浏览器性能问题而被设计出来的。例如前面的例子,假如一次操作中有 10 次更新 DOM 的动作,虚拟DOM不会立即操作DOM,而是将这 10 次更新 DOM 的动作通过Diff算法最终生成一个js对象,然后通知浏览器去执行一次绘制工作,这样可以避免大量的无谓的计算量。

什么是虚拟 DOM

虚拟 DOM[2]就是我们上面所说的js对象。

其本质上就是在JS和DOM之间做了一个缓存。可以类比 CPU 和硬盘,既然硬盘这么慢,我们就在它们之间加个缓存:既然 DOM 这么慢,我们就在它们 JS 和 DOM 之间加个缓存。CPU(JS)只操作内存(Virtual DOM),最后的时候再把变更写入硬盘(DOM), 直接操作内存中的 JS 对象的速度显然要更快。

function vnode(tag, data, key, children, text) { 
    return { 
        tag, 
        data, 
        key, 
        children, 
        text 
    } 
} 

举个栗子:

假如我们有这样的一个 DOM 树

<ul class="list"> 
  <li class="item">前端简报</li> 
  <li>vue</li> 
</ul> 

那么,我们怎么用 js 的对象来对应到这个树呢?

{ 
    tag: 'ul',        // 元素标签 
    data: {           // 属性 
        class: 'list' 
    }, 
    key: '', 
    text: '',  // 文本内容 
    children: [ 
        { 
            tag: "li", 
            data: { 
                class: "item" 
            }, 
            key: '', 
            text: '', 
            children: [ 
                { 
                    tag: undefined, 
                    data: undefined, 
                    key: undefined, 
                    text: '前端简报', 
                    children: [] 
                } 
            ] 
        }, 
        { 
            tag: "li", 
            data: "", 
            key: '', 
            text: '', 
            children: [ 
                { 
                    tag: undefined, 
                    data: undefined, 
                    key: undefined, 
                    text: 'vue', 
                    children: [] 
                } 
            ] 
        } 
    ]       // 子元素 
} 

由此可知:DOM tree的信息都可以用JavaScript对象来表示,反过来,我们也可以用 JavaScript对象表示的树结构来构建一棵真正的DOM树。

实现虚拟 DOM 渲染真实 DOM

有了JavaScript对象之后如何转化为真实的 DOM 树结构呢?

ul 和 li 在 js 对象中,页面上并没有此结构,所以我们需要把ul和li转化为和

标签

而文本标签我们定义 Vnode 为:

{ 
   tag: undefined, 
   data: undefined, 
   key: undefined, 
   text: 'vue', 
   children: [] 
} 

故可以判断tag的类型来确定创建元素的类型.

function createElm(vnode) { 
    let { tag, data, children, key, text } = vnode; 
 
    if (typeof tag == "string") { 
        vnode.el = document.createElement(tag);  //创建元素放到vnode.el上 
        children.forEach(child => { 
            vnode.el.appendChild(createElm(child)) 
        }) 
    } else { 
        vnode.el = document.createTextNode(text);  //创建文本 
    } 
    return vnode.el 
} 

如果子节点存在并且也是虚拟DOM的话,我们通过递归调用创建子节点。

FVvymuN.png!mobile

创建 DOM 树结构之后我们需要设置节点的属性,即处理虚拟 DOM 中的data属性。

function updateProperties(vnode) { 
    let el = vnode.el; 
    let newProps = vnode.data || {}; 
    for (let key in newProps) { 
        if (key == "style") { 
            for (let styleName in newProps.style) { 
                el.style[styleName] = newProps.style[styleName]; 
            } 
        } else if (key == "class") { 
            el.className = newProps.class; 
        } else { 
            el.setAttribute(key, newProps[key]); 
        } 
    } 
} 

在我们创建元素标签之后调用updateProperties方法即可

R3UNnab.png!mobile

把上面创建出来的真实 DOM 结构 vnode.el 添加到文档当中即可呈现出我们需要的真实 DOM 结构

let parentElm = document.getElementById("app").parentNode; 获取之前app的父亲body 
parentElm.insertBefore(createElm(vnode), document.getElementById("app").nextSibling); //body里在老的app后面插入真实dom 
parentElm.removeChild(document.getElementById("app")); //删除老的节点 

rE7jQfb.png!mobile

总结

以上就是本文的全部内容,我想我们现在应该了解什么是虚拟DOM的概念了以及虚拟DOM是如何实现真实DOM渲染的。其中用到了主要用到了子节点的递归,下篇文章将讲解虚拟节点的 diff 算法,敬请期待。

参考资料

[1]虚拟DOM介绍: https://www.jianshu.com/p/616999666920

[2]如何实现一个 Virtual DOM 算法: 'https://github.com/livoras/blog/issues/13'

Z7FJJrE.png!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK