前言
vdom 是 vue 和 React 的核心,先讲哪个都绕不开它
vdom 比较独立,使用也比较简单
如果面试问到 vue 和 React 和实现,免不了问 vdom,带着三个问题去深入了解
问题
-
vdom 是什么?为何会存在 vdom?
-
vdom 的如何应用,核心 API 是什么
-
介绍一下 diff 算法
1、vdom 是什么?为何会存在 vdom?
-
virtual dom,虚拟 DOM
-
用JS模拟DOM的结构
-
DOM 变化的对比,放在 JS 层来做(图灵完备语言)
-
提高重绘性能
DOM结构
-
<ul id='list'>
-
<li class='item'>item1</li>
-
<li class='item'>item2</li>
-
</ul>
JS模拟
-
{
-
tag:'ul',
-
attrs:{
-
id:'list'
-
},
-
children:[
-
{
-
tag:'li',
-
attrs:{
-
className: 'item'
-
},
-
children:['item1']
-
},{
-
tag:'li',
-
attrs:{
-
className: 'item'
-
},
-
children:['item2']
-
}
-
]
-
}
设计一个需求场景
用jQuery实现
遇到的问题
-
DOM的操作是“昂贵”的,js运行效率高
-
尽量减少DOM的操作,而不是推倒重来
-
项目越复杂,影响越严重
-
vdom即可解决这些问题
问题解答
-
virtual dom , 虚拟 DOM
-
用 JS 模拟 DOM 结构
-
DOM 操作非常“昂贵”
-
将 DOM 对比操作放在 JS 层,提高效率
2、vdom 的如何应用,核心 API 是什么
-
介绍 snabbdom (vdom的一个库)
-
重做之前的 demo
-
核心 API
snabbdom 一个注重简单性、模块化、强大功能和性能的虚拟DOM库。
介绍 snabbdom - h 函数
介绍 snabbdom - patch 函数
重做demo
-
// <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom.js"></script>
-
// <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-class.js"></script>
-
// <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-props.js"></script>
-
// <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-style.js"></script>
-
// <script src="https://cdn.bootcss.com/snabbdom/0.7.1/snabbdom-eventlisteners.js"></script>
-
// <script src="https://cdn.bootcss.com/snabbdom/0.7.1/h.js"></script>
-
var snabbdom = window.snabbdom
-
// 定义 patch
-
var patch = snabbdom.init([
-
snabbdom_class,
-
snabbdom_props,
-
snabbdom_style,
-
snabbdom_eventlisteners
-
])
-
// 定义 h
-
var h = snabbdom.h
-
var container = document.getElementById('container')
-
// 生成 vnode
-
var vnode = h('ul#list', {}, [
-
h('li.item', {}, 'Item 1'),
-
h('li.item', {}, 'Item 2')
-
])
-
patch(container, vnode)
-
document.getElementById('btn-change').addEventListener('click', function () {
-
// 生成 newVnode
-
var newVnode = h('ul#list', {}, [
-
h('li.item', {}, 'Item 1'),
-
h('li.item', {}, 'Item B'),
-
h('li.item', {}, 'Item 3')
-
])
-
patch(vnode, newVnode) // 找出差异,渲染差异
-
})
-
// jquery例子改造
-
var snabbdom = window.snabbdom
-
// 定义关键函数 patch
-
var patch = snabbdom.init([
-
snabbdom_class,
-
snabbdom_props,
-
snabbdom_style,
-
snabbdom_eventlisteners
-
])
-
// 定义关键函数 h
-
var h = snabbdom.h
-
// 原始数据
-
var data = [{
-
name: '张三',
-
age: '20',
-
address: '北京'
-
},
-
{
-
name: '李四',
-
age: '21',
-
address: '上海'
-
},
-
{
-
name: '王五',
-
age: '22',
-
address: '广州'
-
}
-
]
-
// 把表头也放在 data 中
-
data.unshift({
-
name: '姓名',
-
age: '年龄',
-
address: '地址'
-
})
-
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) {
-
// re-render
-
patch(vnode, newVnode)
-
} else {
-
// 初次渲染
-
patch(container, newVnode)
-
}
-
// 存储当前的 vnode 结果
-
vnode = newVnode
-
}
-
// 初次渲染
-
render(data)
-
var btnChange = document.getElementById('btn-change')
-
btnChange.addEventListener('click', function () {
-
data[1].age = 30
-
data[2].address = '深圳'
-
// re-render
-
render(data)
-
})
-
使用 data 生成 vnode
-
第一次渲染,将 vnode 渲染到 #container 中
-
并将 vnode 缓存下来
-
修改 data 之后,用新 data 生成 newVnode
-
将 vnode 和 newVnode 对比
核心API:h 函数、patch 函数
-
h(‘<标签名>’, {…属性…}, […子元素…])
-
h(‘<标签名>’, {…属性…}, ‘….’)
-
patch(container, vnode)
-
patch(vnode, newVnode)
介绍一下 diff 算法
什么是diff算法
-
linux diff 命令
-
git diff (对比两个文件之间差异)
去繁就简
-
diff 算法非常复杂,实现难度很大,源码量很大
-
去繁就简,讲明白核心流程,不关心细节
-
面试官也大部分都不清楚细节,但是很关心核心流程
-
去繁就简之后,依然具有很大挑战性,并不简单
vdom 为何用 diff 算法
-
DOM 操作是“昂贵”的,因此尽量减少 DOM 操作
-
找出本次 DOM 必须更新的节点来更新,其他的不更新
-
这个“找出”的过程,就需要 diff 算法
diff 算法的实现流程
-
patch(container, vnode)
-
patch(vnode, newVnode)
核心逻辑:createElement 和 updateChildren
-
// diff 算法实现
-
// code demo
-
function createElement(vnode) {
-
var tag = vnode.tag // 'ul'
-
var attrs = vnode.attrs || {}
-
var children = vnode.children || []
-
if (!tag) {
-
return null
-
}
-
// 创建真实的 DOM 元素
-
var elem = document.createElement(tag)
-
// 属性
-
var attrName
-
for (attrName in attrs) {
-
if (attrs.hasOwnProperty(attrName)) {
-
// 给 elem 添加属性
-
elem.setAttribute(attrName, attrs[attrName])
-
}
-
}
-
// 子元素
-
children.forEach(function (childVnode) {
-
// 给 elem 添加子元素
-
elem.appendChild(createElement(childVnode)) // 递归
-
})
-
// 返回真实的 DOM 元素
-
return elem
-
}
-
// vnode newVnode compare
-
function updateChildren(vnode, newVnode) {
-
var children = vnode.children || []
-
var newChildren = newVnode.children || []
-
children.forEach(function (childVnode, index) {
-
var newChildVnode = newChildren[index]
-
if (childVnode.tag === newChildVnode.tag) {
-
// 深层次对比,递归
-
updateChildren(childVnode, newChildVnode)
-
} else {
-
// 替换
-
replaceNode(childVnode, newChildVnode)
-
}
-
})
-
}
-
function replaceNode(vnode, newVnode) {
-
var elem = vnode.elem // 真实的 DOM 节点
-
var newElem = createElement(newVnode)
-
// 替换
-
}
-
节点新增和删除
-
节点重新排序
-
节点属性、样式、事件变化
-
如何极致压榨性能
-
......
answer:
-
知道什么是 diff 算法,是 linux 的基础命令
-
vdom 中应用 diff 算法是为了找出需要更新的节点
-
vdom 实现过程,createElement 和 updateChildren
-
与核心函数 patch 的关系