Vue源码系列解析课程--虚拟DOM和diff算法(手写h函数)

目录

一、介绍:

二、虚拟节点的属性:

三、如何使用h函数:

四、手写h函数:

1. vnode.js函数:

2. h函数:


一、介绍:

h函数是用来产生虚拟节点

比如这样调用h函数:

h('a',{propr:{href:'https://www.baidu.com/'}},'百度');

将得到这样的虚拟节点:

{"sel":"a","data":{props:{href:"https://www.baidu.com"}},"text":"百度"};

它表示的真正的DOM节点是:

百度

注:从虚拟DOM如何变为真是DOM属于模板引擎(mustache)的内容,而h函数是如何产生虚拟DOM。

二、虚拟节点的属性:

{
    children:undefined, 
    data:{},
    elm:undefined,
    key:undefined,
    sel:"div",
    text:"我是一个盒子"
}

children:子元素,如果没有子元素就是undefined。

data: 属性值。

elm: 这个元素对应的真正的DOM节点,如果是undefined表示这个虚拟节点还没有上树。

key: 唯一标识。

sel:  表示选择器。

text: 文本内容。

三、如何使用h函数:

import { init } from 'snabbdom/init'
import { classModule } from 'snabbdom/modules/class'
import { propsModule } from 'snabbdom/modules/props'
import { styleModule } from 'snabbdom/modules/style'
import { eventListenersModule } from 'snabbdom/modules/eventlisteners'
import { h } from 'snabbdom/h' // helper function for creating vnodes


//创建出patch函数
var patch = init([ 
  classModule, // makes it easy to toggle classes
  propsModule, // for setting properties on DOM elements
  styleModule, // handles styling on elements with support for animations
  eventListenersModule, // attaches event listeners
]);


//创建虚拟节点
const myVnode = h('ul',[
    h('li',{},'苹果'),
    h('li','西瓜'),
    h('li',[
        h('div',[
            h('p','哈哈'),
            h('p','嘻嘻')
        ])
    ]),
    h('li',h('p','火龙果'))
]);


//让虚拟节点上树
var container = document.getElementById('container');
// Second `patch` invocation
patch(container, myVnode); 

注:patch函数是diff算法的核心函数,我们下面的博客将会更新讲解patch函数。

四、手写h函数:

注:此方法我们只考虑三个参数的情况:

h('div',{},[]);
h('div',{},文字);
h('div',{},h());

1. vnode.js函数:

函数的功能很简单:就是把传入的参数组合成对象返回。

//把传入的参数组合成对象返回
export default function(sel,data,children,text,elm){
    const key = data.key;
    return { sel, data, children, text, elm ,key};
}

2. h函数:

注:因为编写的是一个低配版本,所以这个函数必须接受三个参数,缺一不可,相当于它的重载功能较弱。

形态必须是下面的三种之一

   形态一: h('div',{},'文字')

   形态二:h('div',{},[])

   形态三: h('div',{},h())

  • 检查参数的个数  arguments.length!=3
  • 检查第三个参数的类型:如果是String类型和number类型说明此时是形态一,直接返回vnode
  • 检查第三个参数的类型:如果是数组 那么此时是形态二
  • 检查第三个参数的类型:如果是对象 那么此时是形态三

(1):写出大体框架

import vnode from './vnode'

export default function(sel,data,c){

    if(arguments.length != 3){
        throw new Error("h函数只能传入3个参数");
    }
    
    if(typeof c == 'String' || typeof c == 'number'){
        return vnode(sel,data,undefined,c,undefined);
    }else if(Array.isArray(c)){
        //说明现在调用h函数是形态二
    }else if(typeof c == 'object' && c.hasOwnProperty('sel')){
        //说明现在调用h函数是形态三
    }else{
        throw new Error("类型不匹配");
    }
};

(2) 写形态二 :数组中嵌套的还是h函数,而h函数执行的结果一定是一个对象。

  1. 遍历检查c[i]是不是对象
  2. 此时不用执行c[i],因为测试语句中已经有了执行,此时只需收集。
  3. 循环结束了,就说明children收集完毕了,此时就乐意返回虚拟节点。
import vnode from './vnode'

//低配版本h函数 这个函数必须接受三个参数,缺一不可
//相当于它的重载功能较弱
//形态必须是下面的三种之一
/*
    h('div',{},'文字')
     h('div',{},[])
    h('div',{},h())
*/
export default function(sel,data,c){
//    检查参数的个数
    if(arguments.length != 3){
        throw new Errow("低配版,只能传三个参数");
    }
//    检查参数c的类型
    if(typeof c === "string" || typeof c === 'number '){
        //说明现在调用的是形态一
        return vnode(sel,data,undefined,c,undefined);
    }else if(Array.isArray(c)){ //形态2
        let children = [];
        //    遍历c,收集children
        for(let i = 0;i

(3)写形态三:因为测试语句中已经执行,我们只需要收集返回即可。

import vnode from './vnode'

//低配版本h函数 这个函数必须接受三个参数,缺一不可
//相当于它的重载功能较弱
//形态必须是下面的三种之一
/*
    h('div',{},'文字')
     h('div',{},[])
    h('div',{},h())
*/
export default function(sel,data,c){
//    检查参数的个数
    if(arguments.length != 3){
        throw new Errow("低配版,只能传三个参数");
    }
//    检查参数c的类型
    if(typeof c === "string" || typeof c === 'number '){
        //说明现在调用的是形态一
        return vnode(sel,data,undefined,c,undefined);
    }else if(Array.isArray(c)){ //形态2
        let children = [];
        //    遍历c,收集children
        for(let i = 0;i

注:代码地址。

你可能感兴趣的:(web前端,#,Vue)