新虚拟dom和老虚拟dom进行diff算法(精细化比较),算出如何最小量更新,最后反映到真实dom上
diff是发生在虚拟dom上的
模板编译
虚拟dom如何产生 - 渲染函数(h函数)
h函数产生虚拟节点(vnode)
Dom节点:<a href="https://www.baidu.com">真实Dom</a>
调用h函数:h('a',{props:{href:"https://www.baidu.com"}},'真实Dom')
生成虚拟节点:{"sel":"a","data":{props:{href="https://www.baidu.com"}},"text:"真实Dom"}
虚拟节点有哪些属性
{
children:{}, // 子元素
data:{}, // 属性,样式
elm:undefined, // 这个元素真正的dom节点,如果是undefined证明这个虚拟dom还没有上树
key:undefined, // 节点唯一标识
sel:"div", // 选择器
text:"真实Dom" // 文字
}
使用patch函数让虚拟节点上树,一个容器只能让一个虚拟dom上树,除非h函数进行嵌套
import { init, classModule, propsModule, styleModule, eventListenersModule, h } from 'snabbdom'
// 创建patch函数
const patch = init([classModule, propsModule, styleModule, eventListenersModule])
// 创建虚拟节点
var myVnode1 = h('a', { props: { href: 'https://www.baidu.com', target: '_blank' } }, '真实Dom')
// 使用patch函数让虚拟节点上树
const container = document.getElementById('container')
// 一个容器只能让一个虚拟dom上树,除非h函数进行嵌套
patch(container, myVnode2)
h函数可以嵌套使用创建虚拟dom树
var myVnode3 = h('ul', {}, [h('li', '苹果'), h('li', '西瓜'), h('li', '香蕉'), h('li', '火龙果')])
第二个参数写不写不影响
var myVnode21 = h('div', {}, '我是一个盒子')
var myVnode22 = h('div', '我是一个盒子')
第三个参数如果再有一个子元素可以不要数组,如果有多个子元素需要使用数组
var myVnode3 = h('ul', {}, [
h('li', {}, '苹果'),
h('li', '西瓜'),
h('li', [h('div', [h('p', '嘻嘻'), h('p', '哈哈')])]),
h('li', h('span', '榴莲'))
])
h函数的重载(h函数的实现形式)
h('div')
h('div','文字'),
h('div',[])
h('div',h())
h('div',{},'文字'),
h('div',{},[])
h('div',{},h())
vnode函数
// 函数目的:将5个参数组合成一个对象返回
export default function (sel, data, children, text, elm) {
return { sel, data, children, text, elm }
}
h函数的实现
import vnode from './vnode'
// 弱重载:函数重载功能没有实现,必须接收3个参数
// 形态1:h('div',{},'文字')
// 形态2:h('div',{},[])
// 形态3:h('div',{},h())
export default function (sel, data, c) {
// 检查参数个数
if (arguments.length !== 3) throw new Error('低配版,必须传入3个参数')
// 检查参数c的类型
if (typeof c === 'string' || typeof c === 'number') {
// 形态1的h函数:文字
return vnode(sel, data, undefined, c, undefined)
} else if (Array.isArray(c)) {
// 形态2的h函数:数组
let children = []
// 遍历c,收集children
for (let i = 0; i < c.length; i++) {
// c[i]必须是一个有sel属性的对象
if (!(typeof c[i] === 'object' && c[i].hasOwnProperty('sel'))) throw new Error('传入的数组有项不是h函数')
// 不用执行c[i],因为在调用h函数的时候已经执行了
children.push(c[i])
}
// 循环结束,锁门children收集完毕了,可以返回带有children属性的虚拟节点了
return vnode(sel, data, children, undefined, undefined)
} else if (typeof c === 'object' && c.hasOwnProperty('sel')) {
// 形态3的h函数:h函数(一个有sel属性的对象),即的唯一的children
let children = [c]
return vnode(sel, data, children, undefined, undefined)
} else {
throw new Error('传入的第三个参数不对')
}
}
diff算法原理
最小量更新:key很重要,key是这个节点的唯一标识,告诉diff算法,在更改前后他们是同一个dom节点
只有是同一个虚拟dom节点,才能进行精细化比较,否则会暴力删除旧的,插入新的
如果定义是同一个虚拟节点:选择器和key都相同
只进行同层比较,不会进行跨层比较,而是暴力删除旧的,插入新的
diff不是那么无微不至,但是2,3在实际的vue开发中,基本不会遇见
创建节点时,所有子节点都是需要递归创建的
手写第一次上树时