浅析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 原理的时候,应该把各个模块结合到一起去看,才能明白到底是怎么回事。

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

相关推荐
gnip5 小时前
企业级配置式表单组件封装
前端·javascript·vue.js
王王碎冰冰11 小时前
基于 Vue3@3.5+跟Ant Design of Vue 的二次封装的 Form跟搜索Table
前端·vue.js
天蓝色的鱼鱼12 小时前
Element UI 2.X 主题定制完整指南:解决官方工具失效的实战方案
前端·vue.js
我是日安13 小时前
从零到一打造 Vue3 响应式系统 Day 8 - Effect:深入剖析嵌套 effect
前端·vue.js
DevUI团队13 小时前
🚀 MateChat V1.8.0 震撼发布!对话卡片可视化升级,对话体验全面进化~
前端·vue.js·人工智能
好好好明天会更好13 小时前
pinia从定义到运用
前端·vue.js
代码小学僧13 小时前
Vite 项目最简单方法解决部署后 Failed to fetch dynamically imported Error问题
前端·vue.js·vite
东坡白菜14 小时前
SSE 实现 AI 对话中的流式输出
javascript·vue.js
猩兵哥哥18 小时前
前端面向对象设计原则运用 - 策略模式
前端·javascript·vue.js
EMT19 小时前
在 Vue 项目中使用 URL Query 保存和恢复搜索条件
javascript·vue.js