虚拟DOM
vue是MVVM框架的,通过将真实数据渲染到视图中,完成页面展示。当数据发生改变时,会操作DOM,进行视图更新。然而,如果每次改变数据,都直接操作DOM,频繁的操作DOM,会影响性能。所以,vue提供了虚拟DOM的概念。
什么是虚拟DOM?
就是用js对象描述dom的层次结构,dom中的一切属性都在虚拟dom中有对应的属性。
diff精细化比较是发生在新旧两个虚拟dom上的,算出应该如何最小量更新,最后反映到真正的dom上。
h函数
h函数用来产生虚拟节点(vnode)
虚拟节点的属性:
- children:子元素
- data:属性、样式
- elm:对应的真正的dom节点
- key:节点唯一标识
- sel:选择器
- text:文字
// 创建patch函数
const patch=init([classModule,propsModule,styleModule,eventListenersModule]);
// 创建虚拟节点--a标签
var myVnode1=h('a',{props:{href:'http://www.baidu.com'}},'百度');
// console.log(myVnode1)
// 让虚拟节点上树
const container=document.getElementById('container');
patch(container,myVnode1);
h函数可以嵌套使用
const myVnode3=h('ul',[
h('li','西瓜'),
h('li',[
h('button','芒果')
]),
h('li','葡萄')
])
patch(container,myVnode3)

手写h函数
源码中的h函数用if条件判断实现了重载,因为h函数的参数很多,可以传不一样的形式。
自己手写的h函数只实现部分重载,只支持以下三种类型的传参:
h(‘div’,{},‘文字’)
h(‘div’,{},[])
h(‘div’,{},h())
vnode.js:
// 把传入的五个属性组合成对象返回
export default function(sel,data,children,text,elm){
return{
sel,data,children,text,elm
}
}
h.js:
import vnode from './vnode.js'
// 函数只能接收三个参数
// 有以下三种形式的重载
// h('div',{},'文字')
// h('div',{},[])
// h('div',{},h()) h()是个对象,且有sel属性,因为虚拟节点是一定有sel属性的
export default function(sel,data,c){
if(arguments.length!=3){
throw new Error('三个参数奥~');
}
// 判断c的类型
if(typeof c=='string'||typeof c=='number'){// h('div',{},'文字') c是基本类型
return vnode(sel,data,undefined,c,undefined);
}else if(Array.isArray(c)){
let children=[];
for(let i=0;i<c.length;i++){
// 检查c数组的第i项,它必须是一个对象 h函数的执行返回了一个vnode,这是个对象
if(!(typeof c[i]=='object' && c[i].hasOwnProperty('sel'))){
throw new Error('参数类型错误');
}
// 这里不用执行c[i],只需要收集c[i],这是个递归,遍历c的时候,又遇到h函数,调用h函数就已经执行了
children.push(c[i]);
}
// children收集完毕
return vnode(sel,data,children,undefined,undefined)
}else if(typeof c=='object' &&c.hasOwnProperty('sel')){
let children=[c]
return vnode(sel,data,children,undefined,undefined)
}else{
throw new Error('参数类型错误');
}
}
测一下我们的h函数,index.js:
import h from './mysnabbdom/h'
var myNode=h('div',{},[
h('p',{},'哈哈1'),
h('p',{},'哈哈2'),
h('p',{},[
h('p',{},'嘻嘻')
])
])
var myNode2=h('div',{},h('p',{},'123'))
console.log(myNode)
console.log(myNode2)

diff 算法
import {
init,
classModule,
propsModule,
styleModule,
eventListenersModule,
h,
} from "snabbdom";
// 创建patch函数
const patch=init([classModule,propsModule,styleModule,eventListenersModule]);
// 创建vnode
const vnode1=h('ul',{},[
h('li',{},'A'),
h('li',{},'B'),
h('li',{},'C'),
])
// 获取container
const container=document.getElementById('container');
const btn=document.getElementById('btn')
// 上树
patch(container,vnode1);
const vnode2=h('ul',{},[
h('li',{},'A'),
h('li',{},'B'),
h('li',{},'C'),
h('li',{},'D'),
])
btn.onclick=function(){
patch(vnode1,vnode2)
}
这段代码中,我们先是创建了vnode1,让它上树,后又创建了vnode2,vnode2比vnode1相比,多了一个li标签,按照最小量更新,不需要整个重新渲染,只需要在原来的页面中新增一个li标签即可。点击按钮,即可进行更新。
那么,怎么证明它是最小量更新,也就是怎么证明,前边的三个li并没有更新呢?
方法就是在浏览器中修改原来三个li的内容,假设把A改成哈哈哈,点击按钮,如果说,更新策略是整个全部刷新,那哈哈哈还会变回A,并且增加D,如果是最小量更新,哈哈哈保留,并且增加D:

事实证明,的确实最小量更新的。那么,现在,我们改变一下vnode2:
const vnode2=h('ul',{},[
h('li',{},'D'),
h('li',{},'A'),
h('li',{},'B'),
h('li',{},'C'),
])
这个时候点击按钮的效果,是在原来的ul标签的第一行添加了D,按照前边说的,我们想象中,此时的更新是保留之前三个li,在最前边插入内容为D的li,对吧?
再实验一下:

这是点击按钮之前,我们在浏览器中改了A的值,点击按钮:

!!!
这不是我们想象的那样,而是整个ul都重新渲染了。为什么不是最小量更新了?
这里就体现出了key的重要性,前边的代码中,我们还没有涉及过key属性。现在,我们把代码改成下边这个样子:
const vnode1=h('ul',{},[
h('li',{key:'A'},'A'),
h('li',{key:'B'},'B'),
h('li',{key:'C'},'C'),
])
// 获取container
const container=document.getElementById('container');
const btn=document.getElementById('btn')
// 上树
patch(container,vnode1);
const vnode2=h('ul',{},[
h('li',{key:'D'},'D'),
h('li',{key:'A'},'A'),
h('li',{key:'B'},'B'),
h('li',{key:'C'},'C'),
])
btn.onclick=function(){
patch(vnode1,vnode2)
}
测一下:
这就是为什么,v-for循环的时候,添加key会提高效率。
只有同一个虚拟节点,才进行精细化比较,否则就是暴力删除原来节点,插入新节点。同一个虚拟节点的意思是,选择器相同且key值相同。
比如说,我们的vnode2改成ol标签,点击按钮的时候,就是整个大标签的更新,不会比较里边的li。然而,更换父级标签的操作并不常见,所以,这并不影响diff算法效率。
只进行同层比较,不会进行跨层比较。
const vnode2=h('ul',{},h('section',{},[
h('li',{key:'D'},'D'),
h('li',{key:'A'},'A'),
h('li',{key:'B'},'B'),
h('li',{key:'C'},'C'),
]))
如上,仍然是全部更新,删掉原来的vnode1,添加vnode2。
296

被折叠的 条评论
为什么被折叠?



