浅析vue虚拟dom的前世今生

前言

虚拟DOM就是用一个原生的js对象来描述DOM,那么vnode是如何渲染成真实的dom,本文通过手写简单案列简要的分析其原理,主要关注以下几点:

  • Vue中如何描述UI
  • vnode是如何生成的
  • vnode是如何渲染成真实的dom

Vue中如何描述UI

我们编写前端页面都涉及哪些内容,具体如下:

  • DOM元素:例如是div标签还是a标签。
  • 属性:如a标签的href属性,再如id、class等通用属性。
  • 事件:如click、keydown等。
  • 元素的层级结构,DOM树的层级结构,既有子节点,又有父节点。

使用Vue开发时页面是声明式地描述UI,那么,如何声明式的描述上述内容呢?在Vue.js中,哪怕是事件,都有与之对应的描述方式。具体如下:

  • 使用与HTML标签一致的方式来描述DOM元素,例如<div></div>;
  • 使用与HTML标签一致的方式来描述属性,例如<div id="app"></div>;
  • 使用:或v-bind来描述动态绑定的属性,例如<div :id="myApp"></div>;
  • 使用@或v-on来描述事件,例如点击事件<div @click="handler"></div>;
  • 使用与HTML标签一致的方式来描述层级结构,例如一个具有span子节点的div标签<div><span></span></div>

vnode如何生成?

以我们熟悉的.vue文件为例,一个vue文件就是一个组件,如下所示:

js 复制代码
<template>
    <div @click="handler">
        点击按钮
    </div>
</template>
<script>
export default {
    data(){
        return {}
    },
    methods:{
        handler(){
            alert('hello')
        },
    }
}
</script>

编译器会把模板内容编译成渲染函数并添加<script>标签块的组件对象上,所以最终在浏览器里运行的代码就是:

js 复制代码
export default {
    data(){
        return {}
    },
    methods:{
        handler(){
            alert('hello')
        },
    },
    render(){
        return h('div',{onClick:handler},'点击按钮')
    }
}

一个组件要渲染内容是通过渲染函数来描述的,也就是上述代码中的render函数,Vue.js会根据组件的render函数的返回值拿到虚拟DOM。其中上述涉及到的h函数,其实就是一个辅助创建虚拟DOM的工具函数,仅此而已。

vnode是如何渲染成真实的dom

那么虚拟DOM是如何变成真实DOM渲染到浏览器页面中呢?这就用到了我们接下来要介绍的:渲染器

上述渲染函数返回如下虚拟DOM:

js 复制代码
const vnode = {
    tag:'div',
    props:{
        onClick:handler
    },
    children:'点击按钮'
}

接下来,我们编写一个简单的渲染器,把上面这段虚拟DOM渲染为真实DOM:

js 复制代码
function renderer(vnode,container){
    // 使用vnode.tag作为标签名称创建DOM元素
    const el = document.createElement(vnode.tag)
    
    // 遍历vnode.props,将属性、事件添加到DOM元素
    for(const key in vnode.props){
        if(/^on/.test(key)){
            el.addEventListener(
                key.substr(2).toLowerCase(), // 事件名称 onClick-->click
                vnode.props[key] // 事件处理函数
            )
        }
    }
    
    // 处理children
    if(typeof vnode.children === 'string'){
        // 如果是字符串,说明它是元素的文本节点
        el.appendChild(document.createTextNode(vnode.children))
    }else if(vnode.children){
        obj.children.forEach((child)=>renderer(child,el))
    }
    
    // 将元素添加到挂载点下
    container.appendChild(el)
}

这里的 renderer 函数接收如下两个参数。

  • vnode:虚拟 DOM 对象。
  • container:一个真实 DOM 元素,作为挂载点,渲染器会把虚拟 DOM 渲染到该挂载点下。

渲染器其原理是:递归地遍历虚拟DOM对象,并调用原生DOM API来完成真实DOM的创建。渲染器的精髓在于后续的更新,它会通过diff算法找出变更点,并且只会更新需要更新的内容。当然,我们此处只是简单实现其功能。

接下来,我们可以调用 renderer 函数:

js 复制代码
renderer(vnode, document.body) // body 作为挂载点

在浏览器中运行这段代码,会渲染出"点击按钮"文本,点击该文本,会弹出 alert('hello'),如下图所示。

总结

如前所述,虚拟DOM转化成真实DOM的过程,离不开编译器和渲染器这两个非常关键的模块相互配合,并实现性能提升的。可见 Vue.js 的各个模块之间是互相关联、互相制约的,共同构成一个有机整体。因此,我们在学习Vue.js 原理的时候,应该把各个模块结合到一起去看,才能明白到底是怎么回事。

注\]:水平有限,文中如果有错误或表达不当的地方,非常欢迎在评论区指出,感谢~

相关推荐
麦麦大数据33 分钟前
neo4j+django+deepseek知识图谱学习系统对接前后端分离前端vue
vue.js·django·知识图谱·neo4j·deepseek·在线学习系统
三翼鸟数字化技术团队2 小时前
Vue自定义指令最佳实践教程
前端·vue.js
Spark2383 小时前
关于vue3整合tiptap的slash菜单的ts支持
vue.js
随笔记3 小时前
Flex布局下,label标签设置宽度依旧对不齐,完美解决(flex-shrink属性)
javascript·css·vue.js
樊小肆3 小时前
实战!从 0 到 1 搭建 H5 AI 对话页面
前端·vue.js
JiangJiang3 小时前
揭秘Vue3源码之computed:懒计算与缓存机制全解析
前端·vue.js·面试
清灵xmf3 小时前
Vue 3 自定义权限指令 v-action
前端·javascript·vue.js·自定义指令
web_Hsir3 小时前
Uniapp 实现微信小程序滑动面板功能详解
vue.js·微信小程序·uni-app
爱摄影的程序猿5 小时前
如何基于 Django-Vue-Admin 快速二次开发?高效后台管理系统实战指南(附完整代码)
vue.js·python·django
前端极客探险家6 小时前
如何实现一个支持拖拽排序的组件:React 和 Vue 版
前端·vue.js·react.js·排序算法