美文网首页
前端JavaScript高级面试技巧[2]

前端JavaScript高级面试技巧[2]

作者: Mark同学 | 来源:发表于2019-12-08 13:42 被阅读0次

    第5章 虚拟 DOM

    • vdom 是 vue 和 React 的核心
    • vdom 比较独立,使用也比较简单
    • vdom 是 vue 和 React 的核心实现

    题目

    什么是 vdom,为何要用 vdom?

    • 什么是 vdom
    virtual dom , 虚拟 DOM
    用 JS 模拟 DOM 结构
    DOM 变化的对比,放在 JS 层来做(图灵完备语言)
    提高重绘性能 ( DOM 操作是浏览器最耗费性能的操作 )
    
    <!-- DOM 结构 --!>
    <ul id='list'>
      <li class='item'>Item 1</li>
      <li class='item'>Item 2</li>
    </ul>
    
    // 用 JS 模拟 DOM 结构
    {
      tag: 'ul',
      attrs: {
        id: 'list'
      },
      children: [
        {
          tag: 'li',
          attrs: {className: 'item'},
          children: ['Item 1']
        },
        {
          tag: 'li',
          attrs: {className: 'item'},
          children: ['Item 2']
        }
      ]
    }
    
    • 设计一个需求场景
    // 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="./jquery-3.2.1.js"></script>
    <script>
    var data = [
      {
        name: '张三',
        age: '20',
        address: '北京'
      },
      {
        name: '李四',
        age: '21',
        address: '上海'
      },
      {
        name: '王五',
        age: '22',
        address: '广州'
      }
    ];
    </script>
    
    // 渲染函数
    function render(data) {
      // 此处省略 N 行
    }
    
    // 修改信息
    $('#btn-change').click(function () {
      data[1].age = 30;
      data[2].address = '深圳';
      render(data);
    });
    
    // 页面加载完成之后,立即执行 render
    render(data);
    
    // 渲染函数
    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);
    }
    
    • 遇到的问题
    var div = document.createElement('div');
    var item, result = '';
    for (item in div) {
      result += ' | ' + item;
    }
    console.log(result);
    
    DOM 操作是“昂贵”的,js 运行效率高
    尽量减少 DOM 操作,而不是“推倒重来”
    项目越复杂,影响就越严重
    vdom 即可解决这个问题
    

    vdom如何使用,核心 API 有哪些?

    // vdom:用 JS 模拟的 DOM 结构
    // vnode:用 JS 模拟的 DOM 节点
    // h 函数(参数1=选择器,参数2=事件/样式/属性,参数3=变量或数组) {}
    // patch 的两种用法:1.初次渲染。2.再次对比
    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
    
    <div id="container" class="two classes" onclick=''>
      <span style="font-weight: bold;" onclick="javascript:alert('someFn')">This is bold</span>
      and this is just normal text
      <a href="/foo">I'll take you places!</a>
    </div>
    
    • 介绍 snabbdom - h 函数
    // snabbdom 简化版
    var node = h('ul#list', {}, [
      h('li.item', {}, 'Item 1'),
      h('li.item', {}, 'Item 2')
    ]);
    
    // 用 JS 模拟出来的 DOM 结构
    {
      tag: 'ul',
      attrs: {
        id: 'list'
      },
      children: [
        {
          tag: 'li',
          attrs: {className: 'item'},
          children: ['Item 1']
        },
        {
          tag: 'li',
          attrs: {className: 'item'},
          children: ['Item 2']
        }
      ]
    }
    
    • 介绍 snabbdom - patch 函数
    // snabbdom 简化版
    var node = h('ul#list', {}, [
      h('li.item', {}, 'Item 1'),
      h('li.item', {}, 'Item 2')
    ]);
    
    var container = document.getElementById('container');
    patch(container, vnode);
    
    // 模拟改变
    var btnChange = document.getElementById('btn-change');
    btnChange.addEventListener('click', function () {
      var newVnode = h('ul#list', {}, [
        h('li.item', {}, 'Item 111'),
        h('li.item', {}, 'Item 222'),
        h('li.item', {}, 'Item 333')
      ]);
      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">
      <title>v-dom</title>
    </head>
    <body>
      <div id="info-list"></div>
      <button id="btn-change">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.js"></script>
      <script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
      <script>
        var snabbdom = window.snabbdom;
        
        // 定义 patch
        var patch = snabbdom.init([
          snabbdom_class,
          snabbdom_props,
          snabbdom_style,
          snabbdom_eventlisteners
        ]);
    
        // 定义 h
        var h = snabbdom.h;
        
        // 获取真实的占位 DOM
        var infoList = document.getElementById('info-list');  
      
        // 生成 vnode
        var vnode = h('ul#info-list', {}, [
          h('li.item', {}, 'Item 1'),
          h('li.item', {}, 'Item 2')
        ]);
        patch(infoList, vnode); // 真实的占位 DOM 会被 vnode 取代
    
        // 模拟改变
        // 1. 在列表末尾新增一项,原有列表项的 DOM 结构不会重新渲染
        // 2. 在列表中间新增一项,新增项和它后面一项的 DOM 结构都会重新渲染
        // 3. 在列表任何位置删除一项,整个列表的 DOM 结构都会重新渲染
        // 4. 在列表任何位置修改一项,原有列表项的 DOM 结构不会发生变化
        var btnChange = document.getElementById('btn-change');
        btnChange.addEventListener('click', function () {
          var newVnode = h('ul#info-list', {}, [
            h('li.item', {}, 'Item 1'),
            h('li.item', {}, 'Item B'),
            h('li.item', {}, 'Item 3')
          ]);
          patch(vnode, newVnode);
        });
      </script>
    </body>
    </html>
    
    • 重做之前的 demo
    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>v-dom</title>
    </head>
    <body>
      <div id="container"></div>
      <button id="btn-change">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.js"></script>
      <script src="https://cdn.bootcss.com/snabbdom/0.7.3/h.js"></script>
      <script>
        // 原始数据
        var data = [
          {
            name: '张三',
            age: '20',
            address: '北京'
          },
          {
            name: '李四',
            age: '21',
            address: '上海'
          },
          {
            name: '王五',
            age: '22',
            address: '广州'
          }
        ];
    
        // 把表头也放在 data 中
        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');  
    
        // 定义渲染函数
        function render(data) {
          // TODO
        }
      
        // 初次渲染
        render(data);  
      
        var btnChange = document.getElementById('btn-change');
        btnChange.addEventListener('click', function () {
          data[1].age = 30;
          data[2].address = '深圳';
          render(data);
        });
      </script>
    </body>
    </html>
    
        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;
        }
    
    • 核心 API
     h(‘<标签名>’, {…属性…}, […子元素…]);
     h(‘<标签名>’, {…属性…}, ‘….’);
    patch(container, vnode);
    patch(vnode, newVnode);
    

    了解 diff 算法吗?

    • 什么是 diff 算法
    // Linux 里古老的 diff 命令,可以比较两个文本的不同
    >diff log1.txt log2.txt
    // git diff 版本比较
    >git diff ./src/index.js
    // 在线 diff 对比器
    https://tool.oschina.net/diff/
    
    • 去繁就简
    diff 算法非常复杂,实现难度很大,源码量很大
    去繁就简,讲明白核心流程,不关心细节
    面试官也大部分都不清楚细节,但是很关心核心流程
    去繁就简之后,依然具有很大挑战性,并不简单
    
    • vdom 为何用 diff 算法
     DOM 操作是“昂贵”的,因此尽量减少 DOM 操作
    找出本次 DOM 必须更新的节点来更新,其他的不更新
    这个“找出”的过程,就需要 diff 算法
    
    • diff 算法的实现流程
    1.patch(container, vnode);
    2.patch(vnode, newVnode);
    
    1. 虚拟 DOM 如何变成的真实 DOM
    <!-- DOM 结构 --!>
    <ul id='list'>
      <li class='item'>Item 1</li>
    </ul>
    
    // 用 JS 模拟 DOM 结构
    {
      tag: 'ul',
      attrs: {
        id: 'list'
      },
      children: [
        {
          tag: 'li',
          attrs: {className: 'item'},
          children: ['Item 1']
        }
      ]
    }
    
    // vnode 参数就是类似上方的结构
    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;
    }
    
    虚拟 DOM 和真实 DOM 有对应关系
    1. 新的虚拟 DOM 和旧的 DOM 比较
    function updateChildren(vnode, newVnode) {
      var children = vnode.children || [];
      var newChildren = newVnode.children || [];
      
      children.forEach(function (child, index) {
        var newChild = newChildren[index];
        if (newChild == null) {
          return
        }
        if (child.tag === newChild.tag) {
          updateChildren(child, newChild);
        } else {
          replaceNode(child, newChild);
        }
      });
    }
    

    解答

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

    第6章 MVVM

    题目

    之前使用 jquery 和现在使用 Vue 或 React 框架的区别?

    • jQuery 实现 todo-list
    <div>
      <input type="text" name="" id="txt-title"/>
      <button id="btn-submit">提交</button>
    </div>
    <div>
      <ul id="ul-list"></ul>
    </div>
    <script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>
    <script>
      var $txtTitle = $('#txt-title');
      var $ulList = $('#ul-list');
      var $btnSubmit = $('btn-submit');
      $btnSubmit.click(function () {
        var title = $txtTitle.val();
        var $li = $('<li>' + title + '</li>');
        $ulList.append($li);
        $txtTitle.val('');
      });
    </script>
    
    • vue 实现 todo-list
    <div id="app">
      <div>
        <input v-model="title"/>
        <button v-on:click="add">提交</button>
      </div>
      <ul>
        <li v-for="item in list">{{ item }}</li>
      </ul>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script>
      var vm = new Vue({
        el: '#app',
        data: {
          title: '',
          list: []
        },
        methods: {
          add: function () {
            this.list.push(this.title);
            this.title = '';
          }
        }
      });
    </script>
    

    jQuery 和框架的区别

    数据和视图分离 - 解耦
    以数据驱动视图 - 封装DOM 操作
    

    你如何理解MVVM?- 联系 View 和 Model

    View 可以通过 事件绑定 的方式影响 Model
    Model 可以通过 数据绑定 的方式影响 View
    

    第7章 组件化和 React

    • 是否做过 React 开发?
    • React 以及组件化的一些核心概念
    • 实现流程
    //>todo
    //>——index.js
    import React, { Component } from 'react'
    import Input from './input/index.js'
    import List from './list/index.js'
    
    // class Component {
    //     constructor(props) {
    
    //     }
    //     renderComponent() {
    //         const prevVnode = this._vnode
    //         const newVnode = this.render()
    //         patch(prevVnode, newVnode)
    //         this._vnode = newVnode
    //     }
    // }
    
    class Todo extends Component {
        constructor(props) {
            super(props)
            this.state = {
                list: ['a', 'b']
            }
        }
        render() {
            return (
                <div>
                    <Input addTitle={this.addTitle.bind(this)}/>
                    <List data={this.state.list}/>
                </div>
            )
    
            /*
                React.createElement(
                    "div",
                    null,
                    React.createElement(Input, { addTitle: this.addTitle.bind(this) }),
                    React.createElement(List, { data: this.state.list })
                );
            */
    
            // React.createElement(List, { data: this.state.list })
            // var list = new List({ data: this.state.list })
            // var vnode = list.render()
        }
        addTitle(title) {
            const currentList = this.state.list
            this.setState({
                list: currentList.concat(title)
            }
                // , () => {
                //     // console.log(this.state.list)
                //     this.renderComponent()
                // }
            )
        }
    }
    
    export default Todo
    
    
    //>todo
    //>——list
    //>————index.js
    import React, { Component } from 'react'
    
    class List extends Component {
        constructor(props) {
            super(props)
        }
        render() {
            const list = this.props.data
            return (
                <ul>
                    {
                        list.map((item, index) => {
                            return <li key={index}>{item}</li>
                        })
                    }
                </ul>
            )
    
            /*
                React.createElement(
                    "ul",
                    null,
                    list.map((item, index) => {
                        return React.createElement(
                            "li",
                            { key: index },
                            item
                        );
                    })
                );
            */
        }
    }
    
    export default List
    
    
    //>todo
    //>——input
    //>————index.js
    import React, { Component } from 'react'
    
    class Input extends Component {
        constructor(props) {
            super(props)
            this.state = {
                title: ''
            }
        }
        render() {
            return (
                <div>
                    <input value={this.state.title} onChange={this.changeHandle.bind(this)}/>
                    <button onClick={this.clickHandle.bind(this)}>submit</button>
                </div>
            )
        }
        changeHandle(event) {
            this.setState({
                title: event.target.value
            })
        }
        clickHandle() {
            const title = this.state.title
            const addTitle = this.props.addTitle
            addTitle(title) // 重点!!!
            this.setState({
                title: ''
            })
        }
    }
    
    export default Input
    
    

    对组件化的理解?

    • 组件的封装
    视图
    数据
    变化逻辑(数据驱动视图变化)
    
    • 组件的复用
    props 传递
    复用
    

    JSX 是什么?

    • JSX 语法
    html 形式
    引入 JS 变量和表达式
    if…else…
    循环
    style 和 className
    事件
    JSX 语法根本无法被浏览器所解析
    那么它如何在浏览器运行?
    
    • JSX 解析成 JS
    • 独立的标准
    JSX 是 React 引入的,但不是 React 独有的
    React 已经将它作为一个独立标准开放,其他项目也可用
    React.createElement 是可以自定义修改的
    说明:本身功能已经完备;和其他标准监控和扩展性没问题
    另:有机会录制《1000行代码实现React》,就用 JSX 标准
    

    JSX 和 vdom 什么关系?

    简述 React 的 setState?

    简述自己如何比较 React 和 Vue?

    相关文章

      网友评论

          本文标题:前端JavaScript高级面试技巧[2]

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