第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 是一类技术实现
- 介绍 snabbdom——一个开源的 v-dom库
https://github.com/snabbdom/snabbdom
// 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);
- 虚拟 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 有对应关系
- 新的虚拟 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?
网友评论