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

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

相关推荐
猫猫不是喵喵.44 分钟前
Vue2 的 Vuex 状态管理与 Vue3 的 Pinia 状态管理
前端·javascript·vue.js
林焱_RPAAI18 小时前
影刀RPA技术深度:CSS选择器高级实战指南——伪类属性选择器与性能对比完全解析
vue.js·react.js
陳陈陳20 小时前
🚀 前端流式输出革命:SSE + BFF 架构从零到一实战指南
vue.js·架构·node.js
黄泉路醉s1 天前
在Vant+Vue+TypeScript的H移动前端使用UnoCSS
前端·vue.js·typescript
我要两颗404西柚1 天前
Stage three:VUE工程化与实战工具
前端·javascript·vue.js
看昭奚恤哭1 天前
基于 RuoYi-Vue-Pro 定制了一个后台管理系统 magic-admin , 开源出来!
前端·vue.js·开源
妙码生花2 天前
从 PHP 到 AI + Golang,程序员自救转型手记(四十五):前端远程下拉输入组件
前端·javascript·vue.js
Listen·Rain2 天前
AGENTS.md — Vue 3 Frontend Development
前端·javascript·vue.js
南风知我意啊2 天前
Vue3图片缩放拖拽组件全攻略
前端·javascript·vue.js
万亿少女的梦1682 天前
基于Spring Boot、Vue.js与MySQL的流浪猫狗救助系统设计与实现
vue.js·spring boot·mysql·javaweb·权限管理