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

第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?

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,843评论 6 502
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,538评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,187评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,264评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,289评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,231评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,116评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,945评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,367评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,581评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,754评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,458评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,068评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,692评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,842评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,797评论 2 369
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,654评论 2 354