美文网首页
前端面试之虚拟 DOM

前端面试之虚拟 DOM

作者: 小雪狸 | 来源:发表于2020-09-09 16:13 被阅读0次

    vdom 是什么,为何会存在 vdom

    什么是 vdom

    1. virtual dom, 虚拟 DOM
    2. 用 JS 模拟 DOM 结构
    3. DOM 操作非常“昂贵”
    4. DOM 变化的对比,放在 JS 层来做(图灵完备语言)提高效率
    5. 提高重绘性能
    // 真实的 DOM
    <ul id="list">
        <li class="item">Item1</li>
        <li class="item">Item2</li>
    </ul>
    
    // 虚拟 DOM
    {
      tag: 'ul',
      attrs: {
        id: 'list'
      },
      children: [{
        tag: 'li',
        attrs: { calssName: 'item' },
        children: ['Item1']
      }, {
        tag: 'li',
        attrs: { calssName: 'item' },
        children: ['Item2']
      }]
    }
    

    设计一个需求场景

    // 1、将该数据展示成一个表格。 2、随便修改一个信息,表格也跟着更改
    [{
        name: '张三',
        age: '20',
        address: '北京'
    }, {
        name: '李四',
        age: '21',
        address: '上海'
    }, {
        name: '王五',
        age: '22',
        address: '广州'
    }]
    

    用 jQuery 实现

    <div id="container"></div>
    <button id="btn-change">change</button>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
        var data = [{
            name: '张三',
            age: '20',
            address: '北京'
        }, {
            name: '李四',
            age: '21',
            address: '上海'
        }, {
            name: '王五',
            age: '22',
            address: '广州'
        }];
        // 渲染函数
        function render(data) {
          var $container = $('#container')
          // 清空容器
          $container.html('')
        
          // 拼接table
          var $table = $('<table>')
          $table.append($('<tr><td>name</td><td>age</td><td>address</td></tr>'));
          data.forEach(function(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 = '深圳';
          // re-render
          render(data);
        })
        // 页面加载完立刻执行(初次渲染)
        render(data)
    </script>
    

    遇到的问题

    1. DOM 操作是低效的,js 运行效率高
    2. 尽量减少 DOM 操作
    3. 项目越复杂,影响越严重
    4. vdom 可解决这个问题
    var div = document.createElement('div')
    var item, result = '';
    for (item in div) {
      result += '|' + item;
    }
    console.log(result)
    

    打印的内容为:(证明操作DOM 是低效的 )
    |align|title|lang|translate|dir|dataset|hidden|tabIndex|accessKey|draggable|spellcheck|autocapitalize|contentEditable|isContentEditable|inputMode|offsetParent|off...

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

    解答思路

    1. 可用snabbdom 的用法来举例
    2. 核心 API: h 函数, patch 函数

    介绍 snabbdom

    var container = document.getElementById('container')
    
    var vnode = h('div#container.two.classes', { on: { click: someFn } }, [
      h('span', { style: { fontWeight: 'bold' } }, 'This is bold'),
      ' and this is just normal text',
      h('a', { props: { href: '/foo' } }, 'I\'ll take you places!')
    ])
    // Patch into empty DOM element – this modifies the DOM as a side effect
    // 第一次渲染!!!!!!!!!!
    patch(container, vnode)
    
    var newVnode = h('div#container.two.classes', { on: { click: anotherEventHandler } }, [
      h('span', { style: { fontWeight: 'normal', fontStyle: 'italic' } }, 'This is now italic type'),
      ' and this is still just normal text',
      h('a', { props: { href: '/bar' } }, 'I\'ll take you places!')
    ])
    // Second `patch` invocation
    // 非第一次渲染!!!!!!
    patch(vnode, newVnode) // Snabbdom efficiently updates the old view to the new state
    

    重做之前的 demo

      <div id="container"></div>
      <button id="btn-change">change</button>
      <script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.1/snabbdom.js"></script>
      <script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.1/snabbdom-class.js"></script>
      <script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.1/snabbdom-props.js"></script>
      <script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.1/snabbdom-style.js"></script>
      <script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
      <script src="https://cdn.bootcdn.net/ajax/libs/snabbdom/0.7.1/h.js"></script>
      <script>
    var data = [{
        name: '张三',
        age: '20',
        address: '北京'
    }, {
        name: '李四',
        age: '21',
        address: '上海'
    }, {
        name: '王五',
        age: '22',
        address: '广州'
    }];
    data.unshift({
      name: '姓名',
      age: '年龄',
      address: '地址'
    })
    var snabbdom = window.snabbdom;
    var patch = snabbdom.init([
      snabbdom_class,
      snabbdom_props,
      snabbdom_style,
      snabbdom_eventlisteners
    ]);
    var h = snabbdom.h;
    var container = document.getElementById('container');
    
    var vnode;
    function render(data) {
      var newVnode = h('table', {}, data.map(function(item) {
        var tds = [];
        var i;
        for (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);
    var btnChange = document.getElementById('btn-change')
    btnChange.addEventListener('click', function() {
      data[1].age = 30;
      data[2].address = '深圳';
      render(data);
    })
    </script>
    

    核心 API

    1. h('<标签名>', {...属性...}, [...子元素...])
    2. h('<标签名>', {...属性...}, '...')
    3. patch(container, vnode)
    4. patch(vnode, newVnode)

    介绍一下 diff 算法

    解答思路:

    1. 知道什么是 diff 算法,是 linux 的基础命令
    2. vdom 中应用 diff 算法是为了找出需要更新的节点
    3. diff 实现 patch(container, vnode) patch(vnode, newVnode)
    4. 核心逻辑 createElement 和 updateChildren

    diff 算法非常复杂,实现难度大,源码量很大

    去繁就简,讲明白核心流程,不关心细节

    面试官也大部分不清楚细节,但是很关心核心流程

    去繁就简后,依然有很大的挑战性,并不简单

    vdom 为何使用 diff 算法

    1. DOM 操作是昂贵的,因此尽量减少 DOM 操作
    2. 找出本次 DOM 必须更新的节点来更新,其他的不更新
    3. 这个“找出”的过程,就需要 diff 算法

    diff 实现过程

    1. patch(container, vnode)
    2. patch(vnode, newVnode)
    // patch(container, vnode)
    // 虚拟 DOM
    {
      tag: 'ul',
      attrs: {
        id: 'list'
      },
      children: [{
        tag: 'li',
        attrs: { calssName: 'item' },
        children: ['Item1']
      }, {
        tag: 'li',
        attrs: { calssName: 'item' },
        children: ['Item2']
      }]
    }
    
    // 模拟代码(将虚拟 dom 转化为真实 DOM)
    function createElement(vnode) {
        var tag = vnode.tag;
        var attrs = vnode.attrs || {};
        var children = vnode.children || [];
        if (!tag) {
            return null
        }
        // 创建元素
        var elem = document.createElement(tag);
        // 属性
        var attrName;
        for (attrName in attrs) {
            if(attrs.hasOwnProperty(attrName)) {
                elem.setAttribute(attrName. attrs[attrName])
            }
        }
        // 子元素
        children.forEach(function(childVnode) {
            // 递归调用createElement 创建子元素
            elem.appendChild(createElement(childVnode))
        })
        return elem;
    }
    
    // patch(vnode, newVnode) 对比两个 vnode
    function updateChildren(vnode, newVnode) {
        var children = vnode.children || []
        var newChildren = newVnode.children || []
        // 遍历现有的 children
        children.forEach(function(child, index){
            var newChild = newChildren[index]
            if (newChild == null) {
                return
            }
            if (child.tag === newChild.tag) {
                // 两者 tag 一样
                updateChildren(child, newChild)
            } else {
                // 两者 tag 不一样
                replaceNode(child, newChild)
            }
        }) 
    }
    

    另外还有 节点新增和删除、节点重新排序、节点属性、样式、事件绑定、如何极致压榨性能

    相关文章

      网友评论

          本文标题:前端面试之虚拟 DOM

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