vue渲染器的实现

本文通过一个实例展示了虚拟DOM的创建、挂载、更新过程。从h函数创建vnode,到使用mount函数将其挂载到真实DOM,再到通过patch函数进行DOM更新,详细解释了虚拟DOM如何优化DOM操作,提高性能。核心代码包括vnode的定义、mount和patch函数,体现了虚拟DOM的diff算法。
摘要由CSDN通过智能技术生成

虚拟DOM渲染过程: template => render function渲染函数 => vnode => 真实DOM => 视图
vnode是啥❓就是一个普通的javascript对象,或者说是不同类型的DOM元素,简单的说 vnode就是可以理解成节点描述对象
具体实现看代码🔽

  1. index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div id="app"></div>
    <script src="./renderer.js"></script>
    <script>
        /**
         * 1.通过h函数创建一个vnode
         * */
        const vnode = h('div', {class: 'why', id: 'aaa'}, [
            h('h2', null, '当前计数:100'),
            h('button', null, '+1')
        ])
        console.log(vnode);

        //2.通过mount函数,将vnode挂载到div #app上
        mount(vnode, document.querySelector('#app'))

        //3.创建一个新的vnode
        const vnode1 = h('h2', {class: 'style', id: 'aaa'}, '哈哈哈')
        
        //两个vnode做一个diff算法
        setTimeout(() => {
            patch(vnode, vnode1)
        }, 2000)
    </script>
</body>
</html>
  1. renderer.js
const h = (tag, props, children) => {
    //vnode -> javascript对象{}
    return {
        tag, //节点名称
        props, //文本节点(事件)
        children //子节点
    }
}

const mount = (vnode, container) => {
    //1.创建出真实的原生,并且在vnode上保留el
    const el = vnode.el = document.createElement(vnode.tag)
    
    //2.处理props(将虚拟节点的props放到el中)
    if(vnode.props) {
        for(const key in vnode.props) {
            const value = vnode.props[key]
            if(key.startsWith('on')) { //对事件监听的判断
                el.addEventListener(key.slice(2).toLowerCase(), value)
            } else {
                el.setAttribute(key, value); //给el设置属性
            }
        }
    }

    //3.处理children
    if(vnode.children) {
        if(typeof vnode.children === "string") {
            el.textContent = vnode.children;
        } else {
            vnode.children.forEach(item => {
                mount(item, el) //递归调用
            })
        }
    }

    //4.将el挂载到container中
    container.appendChild(el);
}

const patch = (n1, n2) => {
    if(n1.tag !== n2.tag) {
       const n1ElPrent =  n1.el.parentElement;
       n1ElPrent.removeChild(n1.el);
       mount(n2, n1ElPrent)
    } else {
        //1.取出element对象,并且在n2中进行保存
        const el = n2.el = n1.el;

        //2.处理props
        const oldProps = n1.props || {}
        const newProps = n2.props || {}
        //2.1.获取所有的newProps添加到el
        for(const key in newProps) {
            const oldValue = oldProps[key]
            const newValue = newProps[key]
            if(newValue !== oldValue) {
                if(key.startsWith('on')) { //对事件监听的判断
                    el.addEventListener(key.slice(2).toLowerCase(), newValue)
                } else {
                    el.setAttribute(key, newValue); //给el设置属性
                }
            }
        }
        //2.2.删除旧的props
        for(const key in oldProps) {
            if(!(key in newProps)) {
                if(key.startsWith('on')) { //对事件监听的判断
                    const value = oldProps[key]
                    el.removeEventListener(key.slice(2).toLowerCase(), value)
                } else {
                    el.removeAttribute(key); //给el设置属性
                }
            }
        }

        //3.处理children
        const oldChildren = n1.children || []
        const newChildren = n2.children || []

        if(typeof newChildren === "string") {
            //边界情况
            if(typeof oldChildren === 'string') {
                if(newChildren !== oldChildren) {
                    el.textContent = newChildren
                }
            } else {
                el.innerHTML = newChildren
            }
        } else {
            if(typeof oldChildren === 'string') {
                el.innerHTML = ''
                newChildren.forEach(item => {
                    mount(item, el)
                })
            } else {
                //oldChilren和newChildren进行Diff算法
                //前面有相同节点的元素进行patch操作
                const commonLength = Math.min(oldChildren.length, newChildren.length) //比较长度 拿到更短的长度
                for (let i = 0; i < commonLength; i++) {
                    patch(oldChildren[i], newChildren[i])            
                }
                
                //newchildren更长 需要将多余的节点挂载
                if(newChildren.length > oldChildren.length) {
                    //切割遍历
                    newChildren.slice(oldChildren.length).forEach(item => {
                        mount(item, el)
                    })
                }

                //newchildren的length小于oldChildren
                if(newChildren.length < oldChildren.length) {
                    oldChildren.slice(newChildren.length).forEach(item => {
                        //移除多余的子节点
                        el.removeChild(item.el)

                    })
                }
            }
        }
    }
}
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值