虚拟DOM和Diff算法

虚拟DOM (virtual dom)

  • vdom 是 vue 和 react 的核心,学习他们绕不开vdom
  • vdom比较独立,使用也比较简单
  • 如果面试问到了vue和react的实现,免不了问vdom

问题:

  • 什么是vdom?为何会存在vdom?
  • vdom如何应用,核心API是什么?
  • 介绍一下diff算法

什么是vdom?为何会存在vdom

  • virtual dom , 虚拟dom
  • 用js模拟dom结构
  • dom的变化对比,放在js层来做(图灵完备语言)
  • 提高重绘性能

图灵完备的语言:简单来说就是能实现任何数学逻辑的语言。

对浏览器来说,dom操作比执行js更加耗费性能.所以使用vdom来模拟dom结构,再用diff算法来比较出dom的变化,最后只渲染变动的部分。要比把大块的dom树结构整个重新渲染,高效的多。所以vdom就有了实际价值和意义。

vdom之前,我们直接使用jQuery操作dom,但有很多问题:

  • dom操作很昂贵,相比之下js的执行效率很高
  • 所以应该尽量减少dom操作,而不是替换整块dom树
  • 项目越复杂,影响越严重
  • vdom就可以解决以上问题

vdom如何应用?核心API是什么?

我们说的vdom不是特指,是统称一类技术实现,有很多不同的库来实现。

  • 介绍snabbdom(开源的vdom库,vue2.0用了)
  • 核心api

<ul id="list">
    <li class="item">item1li>
    <li class="item">item2li>
ul>
// html对应的vdom结构
{
    tag: 'ul',
    attrs: { id: 'list' },
    children: [
        {
            tag: 'li',
            attrs: { className: 'item' },
            children: ['item1']
        },
         {
            tag: 'li',
            attrs: { className: 'item' },
            children: ['item2']
        }
    ]
}

// 使用 snabbdom 的 h 函数来生成上边这个vdom
let vnode = h('ul#list', {}, [
    h('li.item', {}, ['item1']),
    h('li.item', {}, ['item2']),
]);

// 由此可以看出: h 函数用来生成虚拟节点vnode,他的参数为
h('选择器字符串', '描述对象', '子元素数组或文本字符串')

// patch 函数
// 第一次渲染时,把vnode塞入container
// 参数:空的容器节点, 要在其中渲染的虚拟dom
patch(container, vnode)
// 第二次渲染时,对比两个vnode,只更新必要的部分
// 参数:旧的vnode,新的vnode
patch(vnode, newVnode)

介绍一下diff算法

  • 什么是diff算法
  • diff算法很复杂,这里删繁就简,只提及核心主干
  • vdom为何用diff算法
  • diff算法的实现流程

Linux中就有一个基本的diff命令diff 1.txt 2.txt来比较两个文件的内容。git中也有diff,来比较改动。前端vue,react中的diff并非独创。

diff算法复杂,实现难度大,源码量大。这里我们去繁就简,讲明白核心流程,不关心细节。但即便如此,依然不简单。

vdom为何使用diff算法:dom操作昂贵,尽量减少;用diff算法找出不同,只更新必要的节点

diff的实现过程

上节讲了snabbdom库的patch函数,他有两种用法

  • patch(container, vnode)
  • patch(vnode, newVnode)

patch(container, vnode)这种用法,我们把重点放在如何把表示vdom的js对象变成html;

// 将vdom转化为真实的dom
// 此处只用伪代码写明思路
function createElement(vnode) {
    var tag = vnode.tag;
    var attrs = vnode.attrs || {};
    var children = vnode.children || [];
    if(!tag) return null
    // 创建真实的dom元素
    var elem = document.createElement(tag);
    // 设置属性
    for(var attrName in attrs) {
        if(attrs.hasOwnProperty(attrName)) {
            elem.setAttribute(attrName, attrs[attrName])
        }
    }
    // 递归生成并插入子元素
    children.forEach(function(child) {
         elem.appendChild(createElement(child));
    });
    // 返回真实的dom元素
    return elem
}

patch(vnode, newVnode)这种用法,我们把重点放在如何比较两个vdom的不同

// 比较两个vdom的区别
// 此处只用伪代码写明思路
// 实际上根元素是不变的 如vue,react中的: 
// 所以只要比较子元素 function updateChildren(vnode, newVnode) { var children = vnode.children || []; var newChildren = newVnode.children || []; // 遍历子元素 children.forEach(function(childVnode, index) { var newChildVnode = newChildren[index]; // 新旧比较tag是否相同 if(childVnode.tag === newChildVnode.tag) { // tag相同则递归对比下一层子元素 updateChildren(childVnode, newChildVnode); } else { // tag不同则直接替换该tag replaceNode(childVnode, newChildVnode) } }) } // 替换节点 function replaceNode(vnode, newVnode) { var elem = vnode.elem; // 通过现有vnode对应找到真实的dom节点 var newElem = createElement(newVnode); // 替换 }

上边只是大概讲了核心思路,还有其他更多更复杂的内容

你可能感兴趣的:(学习笔记,vue.js,React)