面试官问: 如何理解Virtual DOM?

1、vdom是什么?

vdom是虚拟DOM(Virtual DOM)的简称,指的是用JS模拟的DOM结构,将DOM变化的对比放在JS层来作。换而言之,vdom就是JS对象。javascript

以下DOM结构:css

<ul id="list">
    <li class="item">Item1</li>
    <li class="item">Item2</li>
</ul>
复制代码

映射成虚拟DOM就是这样:html

{
    tag: "ul",
    attrs: {
        id:&emsp;"list"
    },
    children: [
        {
            tag: "li",
            attrs: { className: "item" },
            children: ["Item1"]
        }, {
            tag: "li",
            attrs: { className: "item" },
            children: ["Item2"]
        }
    ]
} 
复制代码

2、为何要用vdom?

如今有一个场景,实现如下需求:前端

[
    {
        name: "张三",
        age: "20",
        address: "北京"
    },
    {
        name: "李四",
        age: "21",
        address: "武汉"
    },
    {
        name: "王五",
        age: "22",
        address: "杭州"
    },
]
复制代码

将该数据展现成一个表格,而且随便修改一个信息,表格也跟着修改。 用jQuery实现以下:vue

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <div id="container"></div>
  <button id="btn-change">改变</button>

  <script src="https://cdn.bootcss.com/jquery/3.2.0/jquery.js"></script>
  <script> const data = [{ name: "张三", age: "20", address: "北京" }, { name: "李四", age: "21", address: "武汉" }, { name: "王五", age: "22", address: "杭州" }, ]; //渲染函数 function render(data) { const $container = $('#container'); $container.html(''); const $table = $('<table>'); // 重绘一次 $table.append($('<tr><td>name</td><td>age</td><td>address</td></tr>')); data.forEach(item => { //每次进入都重绘 $table.append($(`<tr><td>${item.name}</td><td>${item.age}</td><td>${item.address}</td></tr>`)) }) $container.append($table); } $('#btn-change').click(function () { data[1].age = 30; data[2].address = '深圳'; render(data); }); </script>
</body>
</html>

复制代码

这样点击按钮,会有相应的视图变化,可是你审查如下元素,每次改动以后,table标签都得从新建立,也就是说table下面的每个栏目,不论是数据是否和原来同样,都得从新渲染,这并非理想中的状况,当其中的一栏数据和原来同样,咱们但愿这一栏不要从新渲染,由于DOM重绘至关消耗浏览器性能。java

所以咱们采用JS对象模拟的方法,将DOM的比对操做放在JS层,减小浏览器没必要要的重绘,提升效率。node

固然有人说虚拟DOM并不比真实的DOM快,其实也是有道理的。当上述table中的每一条数据都改变时,显然真实的DOM操做更快,由于虚拟DOM还存在js中diff算法的比对过程。因此,上述性能优点仅仅适用于大量数据的渲染而且改变的数据只是一小部分的状况。react

虚拟DOM更加优秀的地方在于:jquery

一、它打开了函数式的UI编程的大门,即UI = f(data)这种构建UI的方式。linux

二、能够将JS对象渲染到浏览器DOM之外的环境中,也就是支持了跨平台开发,好比ReactNative。

另外你们能够参考尤大的一些回答: www.zhihu.com/question/31…

3、使用snabbdom实现vdom

snabbdom地址:github.com/snabbdom/sn…

这是一个简易的实现vdom功能的库,相比vue、react,对于vdom这块更加简易,适合咱们学习vdom。vdom里面有两个核心的api,一个是h函数,一个是patch函数,前者用来生成vdom对象,后者的功能在于作虚拟dom的比对和将vdom挂载到真实DOM上。

简单介绍一下这两个函数的用法:

  • h('标签名', {属性}, [子元素])
  • h('标签名', {属性}, [文本])
  • patch(container, vnode) container为容器DOM元素
  • patch(vnode, newVnode)

如今咱们就来用snabbdom重写一下刚才的例子:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <div id="container"></div>
  <button id="btn-change">改变</button>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-class.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-props.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-style.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/snabbdom-eventlisteners.min.js"></script>
  <script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
  <script> let snabbdom = window.snabbdom; // 定义patch let patch = snabbdom.init([ snabbdom_class, snabbdom_props, snabbdom_style, snabbdom_eventlisteners ]); //定义h let h = snabbdom.h; const data = [{ name: "张三", age: "20", address: "北京" }, { name: "李四", age: "21", address: "武汉" }, { name: "王五", age: "22", address: "杭州" }, ]; data.unshift({name: "姓名", age: "年龄", address: "地址"}); let container = document.getElementById('container'); let vnode; const render = (data) => { let newVnode = h('table', {}, data.map(item => { let tds = []; for(let i in item) { if(item.hasOwnProperty(i)) { tds.push(h('td', {}, item[i] + '')); } } return h('tr', {}, tds); })); if(vnode) { patch(vnode, newVnode); } else { patch(container, newVnode); } vnode = newVnode; } render(data); let btnChnage = document.getElementById('btn-change'); btnChnage.addEventListener('click', function() { data[1].age = 30; data[2].address = "深圳"; //re-render render(data); }) </script>
</body>
</html>
复制代码

再进入页面:

你会发现,只有改变的栏目才闪烁,也就是进行重绘,数据没有改变的栏目仍是保持原样,这样就大大节省了浏览器从新渲染的开销。

4、diff算法

一、什么是diff算法?

所谓diff算法,就是用来找出两段文本之间的差别的一种算法。

做为一个前端,你们常常会听到diff算法这个词,其实diff并非前端原创的算法,其实这一个算法早已在linux的diff命令中有所体现,而且你们经常使用的git diff也是运用的diff算法。

二、vdom为何用diff算法

DOM操做是很是昂贵的,所以咱们须要尽可能地减小DOM操做。这就须要找出本次DOM必须更新的节点来更新,其余的不更新,这个找出的过程,就须要应用diff算法。

三、vdom中diff算法的简易实现

如下代码只是帮助你们理解diff算法的原理和流程,不可用于生产环境。

将vdom转化为真实dom:

const createElement = (vnode) => {
  let tag = vnode.tag;
  let attrs = vnode.attrs || {};
  let children = vnode.children || [];
  if(!tag) {
    return null;
  }
  //建立元素
  let elem = document.createElement(tag);
  //属性
  let attrName;
  for (attrName in attrs) {
    if(attrs.hasOwnProperty(attrName)) {
      elem.setAttribute(attrName, attrs[attrName]);
    }
  }
  //子元素
  children.forEach(childVnode => {
    //给elem添加子元素
    elem.appendChild(createElement(childVnode));
  })

  //返回真实的dom元素
  return elem;
}
复制代码

用简易diff算法作更新操做:

function updateChildren(vnode, newVnode) {
  let children = vnode.children || [];
  let newChildren = newVnode.children || [];

  children.forEach((childVnode, index) => {
    let newChildVNode = newChildren[index];
    if(childVnode.tag === newChildVNode.tag) {
      //深层次对比, 递归过程
      updateChildren(childVnode, newChildVNode);
    } else {
      //替换
      replaceNode(childVnode, newChildVNode);
    }
  })
}
复制代码

参考资料:

知乎尤雨溪回答

揭秘一线互联网企业 前端JavaScript高级面试

前端进阶与面试指南

相关文章
相关标签/搜索