Vue底层原理之虚拟DOM(1)理解h函数&精细化比较

虚拟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。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值