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

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

相关推荐
racerun20 分钟前
vue VueResource & axios
前端·javascript·vue.js
Calm55041 分钟前
Vue3:uv-upload图片上传
前端·vue.js
新中地GIS开发老师1 小时前
《Vue进阶教程》(12)ref的实现详细教程
前端·javascript·vue.js·arcgis·前端框架·地理信息科学·地信
漫天转悠1 小时前
Vue3中404页面捕获(图文详情)
vue.js
Cachel wood2 小时前
Django REST framework (DRF)中的api_view和APIView权限控制
javascript·vue.js·后端·python·ui·django·前端框架
天天进步20153 小时前
Vue项目重构实践:如何构建可维护的企业级应用
前端·vue.js·重构
2402_857583493 小时前
“协同过滤技术实战”:网上书城系统的设计与实现
java·开发语言·vue.js·科技·mfc
小华同学ai3 小时前
vue-office:Star 4.2k,款支持多种Office文件预览的Vue组件库,一站式Office文件预览方案,真心不错
前端·javascript·vue.js·开源·github·office
k09333 小时前
vue中proxy代理配置(测试一)
前端·javascript·vue.js
@解忧杂货铺9 小时前
前端vue如何实现数字框中通过鼠标滚轮上下滚动增减数字
前端·javascript·vue.js