十二、虚拟 DOM 和 render() 函数(1)

news2025/1/12 8:55:27

本章概要

  • 虚拟DOM
  • render()函数

Vue.js 之所以执行性能高,一个很重要的原因就是它的虚拟 DOM 机制。

12.1 虚拟 DOM

浏览器在解析 HTML 文档时,会将文档中的元素、注释、文本等标记按照它们的层级关系组织成一棵树,这就是熟知的 DOM 树。元素、文本等是作为一个个 DOM 节点而存在的,对元素、文本的操作就是对 DOM 节点的操作。

一个元素要想呈现在页面中,必须在 DOM 树中存在该节点,这也是在使用 DOM API 创建元素后,一定要将该元素节点添加到现有 DOM 树中的某个节点下才能渲染到页面中的原因。同样的,删除某个元素实际上就是从 DOM 树中删除该元素对应的节点。每一次对 DOM 的修改都会引起浏览器对网页的重新渲染,这个过程是比较耗时的。

因为早期的 Web 应用中页面的局部刷新不会很多,所以对 DOM 进行操作的次数也就比较少,对性能的影响微乎其微,而现阶段由于单页应用程序的流行,页面的跳转、更新等都是在同一个页面中完成的,自然对 DOM 的操作也就愈发频繁,作为一款优秀的前端框架,必然要考虑 DOM 渲染效率的问题。

Vue.js 2.0 与 React 采用了相同的方案,在 DOM 之上增加一个抽象层来解决渲染效率的问题,这就是虚拟 DOM 。Vue 3.0 重写了虚拟 DOM 的实现,性能更加优异。虚拟 DOM 使用普通的 JavaScript 对象描述 DOM 元素,在 Vue.js 中,每一个虚拟节点都是一个 VNode 实例。

虚拟 DOM 是普通的 JavaScript 对象,访问 JavaScript 对象自然比访问真实 DOM 要快很多。Vue 在更新真实 DOM 钱,会比较更新前后虚拟 DOM 结构中有差异的部分,然后采用异步封信队列的方式将差异部分更新到真实 DOM 中,从而减少了最终要在真实 DOM 上执行的操作次数,提高了页面渲染的效率。

12.2 render() 函数

Vue 推荐在大多数情况下使用模板构建 HTML 。然而,在一些场景中,可能需要 JavaScript 的编程能力,这时可以使用 render() 函数,它比模板更接近编译器。

后面做一个实际应用中的例子。一个问答页面,用户可以单击某个问题链接,跳转到对应的回答部分,也可以单击“返回顶部”链接,回到页面顶部。这可以通过 a 标签的锚链接实现的。

带有锚点的标题的基础代码

<h1>
    <a name="hello-world" href="#hello-world">
        Hello world!
    </a>
</h1>

如果采用组件实现上述代码,考虑到标题可以变化(h1-h6),将标题的级别(h1-h6)定义成组件的 prop ,这样在调用组件时,就可以通过 prop 动态设置标题元素的级别。组件的使用形式如下。

<anchored-heading :level="1">Hello world!</anchored-heading>

接下来就是组件的实现代码。

const app = Vue.createApp({})
app.component('anchored-heading',{
  template:`
    <h1 v-if="level === 1">
        <slot></slot>
    </h1>
    <h2 v-else-if="level === 2">
        <slot></slot>
    </h2>
    <h3 v-else-if="level === 2">
        <slot></slot>
    </h3>
    <h4 v-else-if="level === 2">
        <slot></slot>
    </h4>
    <h5 v-else-if="level === 2">
        <slot></slot>
    </h5>
    <h6 v-else-if="level === 2">
        <slot></slot>
    </h6>
  `,
  props:{
    level:{
      type:Number,
      required:true
    }
  }
})

虽然模板在大多数组件中都非常好用,但在此处不太合适,模板代码冗长,且 slot 元素在每一级元素中都重复书写了。当添加锚元素时,还必须在每个 v-if/v-else-if 分支中再次复制 slot 元素。

下面改用 render() 函数重写上述代码。如下:

const app = Vue.createApp({})
app.component('anchored-heading', {
    render() {
        const { h } = Vue

        return h(
            'h' + this.level, // tag name
            {}, // props/attributes
            this.$slots.default() // array of children
        )
    },
    props: {
        level: {
            type: Number,
            required: true
        }
    }
})

slots 用于以编程方式访问由插槽分发的内容。每个命名的插槽都有其相应的属性(例如,v-solt:foo 的内容将在 this.slots.foo() 中找到)。this.slots.default() 属性包含了所有未包含在命名插槽中的节点或 v-solt:default 的内容。
组件的调用代码如下:

<anchored-heading :level="3">
    <a name="hello-world" href="#hello-world">
        Hello world!
    </a>
</anchored-heading>

完整代码如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title></title>
</head>

<body>

    <div id="app">
        <anchored-heading :level="3">
            <a name="hello-world" href="#hello-world">
                Hello world!
            </a>
        </anchored-heading>
    </div>

    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const app = Vue.createApp({})
        app.component('anchored-heading', {
            render() {
                const { h } = Vue

                return h(
                    'h' + this.level, // tag name
                    {}, // props/attributes
                    this.$slots.default() // array of children
                )
            },
            props: {
                level: {
                    type: Number,
                    required: true
                }
            }
        })
        app.mount('#app')
    </script>
</body>

</html>

渲染结果如下:
在这里插入图片描述

render() 函数中最重要的就是 h() 函数了,下面是 return 语句的代码:

return h(
    'h' + this.level, // tag name
    {}, // props/attributes
    this.$slots.default() // array of children
)
  • h() 函数返回的并不是一个真正的 DOM 元素,它返回的是一个纯 JavaScript 对象,其中包含想 Vue 描述应该在页面上渲染的节点类型的信息,包括任何子节点的描述,也就是虚拟节点(VNode)。
  • h() 函数的作用是创建 VNode ,或许将其命名为 createVNode() 更准确,但由于频繁且为了简洁,将其命名为 h()。
  • h() 函数可以带 3 个参数,第一个参数是必须的,形式为 {String|Object|Function} ,即该参数可以是字符串(HTML 标签名)、对象(组件或一个异步组件)、函数对象(解析前两者之一的 async 函数);第二个参数是可选的,形式为 {Object},表示一个与模板中元素属性对应的数据对象;第三个参数也是可选的,用于生成子虚拟节点,形式为 {String|Array|Object},即该参数可以是字符串(文本虚拟节点)、数组(子虚拟节点的数组)、对象(带插槽的对象)。

以下是 h() 函数可以接收的各种参数的形式:

// @returns {VNode}
h(
  // ----------------第一个参数,必填项----------------
  // {String|Object|Function} tag
  // 一个HTML标签名、组件或异步组件,或者解析上述任何一种的一个 async() 函数
  'div',
  // ----------------第二个参数,可选----------------
  // {Object} props
  // 一个与模板中元素属性(包括普通属性、prop 和事件属性)对应的数据对象
  {},
  // ----------------第三个参数,可选----------------
  // {String|Array|Function} children
  // 子虚拟节点(VNode) 由 h() 函数构建而成
  // 也可以使用字符串来生成“文本虚拟节点”或带有插槽的对象
  [
    '先写一些文本',
    h('h1','一级标题'),
    h(MyComponent,{
      someProp:'foobar'
    })
  ]
)

简单来说:
h() 函数的第一个参数是要创建的元素节点的名字(字符串形式)或组件(对象形式);
第二个参数是元素的属性集合(包括普通属性、prop、事件属性等),以对象形式给出;
第三个参数是子节点信息,以数组形式给出,如果该元素只有文本子节点,则直接以字符串形式给出即可,如果还有子元素,则继续调用 h() 函数。

下面继续完善 anchored-heading 组件,将标题元素的子元素 a 也放到 render() 函数中构建。如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="UTF-8">
    <title></title>
</head>

<body>
    <div id="app">
        <anchored-heading :level="3">
            Hello world!
        </anchored-heading>
    </div>

    <script src="https://unpkg.com/vue@next"></script>
    <script>
        const app = Vue.createApp({})

        function getChildrenTextContent(children) {
            return children
                .map(node => {
                    return typeof node.children === 'string'
                        ? node.children
                        : Array.isArray(node.children)
                            ? getChildrenTextContent(node.children)
                            : ''
                })
                .join('')
        }

        app.component('anchored-heading', {
            render() {
                // 从子节点的文本内容创建kebab-case 风格的 ID
                const headingId = getChildrenTextContent(this.$slots.default())
                    .toLowerCase()
                    .replace(/\W+/g, '-')     // 将非单词字符替换为短划线
                    .replace(/(^-|-$)/g, '')  // 删除前导和尾随的短划线

                return Vue.h('h' + this.level, [
                    Vue.h(
                        'a',
                        {
                            name: headingId,
                            href: '#' + headingId
                        },
                        this.$slots.default()
                    )
                ])
            },
            props: {
                level: {
                    type: Number,
                    required: true
                }
            }
        })
        app.mount('#app')
    </script>
</body>

</html>

渲染结果如下:
在这里插入图片描述

组件树中的所有 VNode 必须是唯一的。例如下面的 render() 函数是不合法的。

rener(){
  const myVNode = Vue.h('p','hi')
  return Vue.h('div',[
    // 错误 : 重复的 VNode
    myVNode,myVNode
  ])
}

如果真的需要重复很多相同元素或组件,可以使用工厂函数实现。例如,下面的 render() 函数用完全合法的方式渲染了 20 个相同的段落。

rener() {
  return Vue.h('div',
    Array.apply(null,{length:20}).map(() => {
      return Vue.h('p','hi')
    })
  )
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/31232.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MCE | LYTAC 与靶向蛋白降解技术

靶向蛋白降解 (TPD) 是一种有效性的&#xff0c;高度选择性的诱发蛋白降解方式。近年来&#xff0c;以 PROTAC 为代表的 TPD 技术的研究如火如荼。PROTAC 主要降解的是胞内蛋白&#xff0c;实际上&#xff0c;有 40% 的基因产物为胞外和膜相关蛋白&#xff0c;如生长因子、细胞…

一种新的数据聚类启发式优化方法——黑洞算法(基于Matlab代码实现)

&#x1f352;&#x1f352;&#x1f352;欢迎关注&#x1f308;&#x1f308;&#x1f308; &#x1f4dd;个人主页&#xff1a;我爱Matlab &#x1f44d;点赞➕评论➕收藏 养成习惯&#xff08;一键三连&#xff09;&#x1f33b;&#x1f33b;&#x1f33b; &#x1f34c;希…

【学习QT必备的C++基础】C++类和对象

文章目录C类的定义和对象的创建详解类的定义创建对象访问类的成员使用对象[指针](http://c.biancheng.net/c/80/)总结C类的成员变量和成员函数详解在类体中和类体外定义成员函数的区别C类成员的访问权限以及类的封装简单地谈类的封装对private和public的更多说明C对象的内存模型…

异构网络小入

A Survey of Heterogeneous Information Network Analysis Heterogeneous Graph Attention Network 异构网络很火吗&#xff1f; 在一个网络中&#xff0c;不用节点的类型不同&#xff0c;这是肯定的。 所以&#xff0c;异构网络在表征比较复杂的情形时&#xff0c;是比较合适…

低代码引擎半岁啦,来跟大家唠唠嗑...

作者&#xff1a;刘菊萍(絮黎) 之前低代码引擎一直是在阿里集团内部进行孵化的&#xff0c;对外开源算是一次新生。从2022年3月23日开源至今&#xff0c;我们的低代码引擎已经半岁了&#xff0c;希望借这个机会来跟大家唠唠嗑。 开源地址&#xff1a;https://github.com/aliba…

WeOps上新|V3.12版本提升网络设备的自动化能力

本次WeOpsV3.12版本更新主题总结为&#xff1a; 持续补充自动发现能力&#xff0c;覆盖网络设备和数据库扩充自动化运维&#xff0c;支持网络设备脚本工具内置AD账号自动化流程&#xff0c;支持AD账号创建/密码重置/删除增加监控告警移动端&#xff0c;覆盖更多场景优化凭据管…

WebDAV之葫芦儿·派盘+人生Life

人生Life 支持webdav方式连接葫芦儿派盘。 自己经常容易忘记的情,后来便记录在便签中,以作提醒和备忘;但是到了时间还是会因时间安排不妥冲突而无法完成,拖拖拉拉、还是很乱,总是会忘记会议、想不起重要的行程,非常烦恼;直到体验了人生Life这几款日程、待办事情的APP后…

WordPress图片自动添加alt标签和title的两种方法(插件or代码)实例

使用WordPress家长朋友知道&#xff0c;上传图片的时候是不会自动添加alt标签的&#xff0c;从seo和用户体验来看&#xff0c;图片加alt标签还是非常非常重要的。 WordPress自动给图片添加alt标签和title的两种方法&#xff08;插件or代码&#xff09;实例Alt信息&#xff1a; …

MemArts :高效解决存算分离架构中数据访问的组件

摘要&#xff1a;计算侧需要一个高速的缓存层来消除计算集群和OBS之间的数据访问鸿沟。为了解决这个问题&#xff0c;提出MemArts CC分布式客户端缓存。本文分享自华为云社区《华为云全新缓存生态组件MemArts》&#xff0c;作者&#xff1a; MichaelYun。 公有云的基础设施都是…

piwigo搭建在线网络相册,并随时随地访问

作为一个打工人&#xff0c;我们所在的城市总与父母相隔甚远&#xff0c;而成家立业后&#xff0c;孩子又成所有人的羁绊&#xff0c;有的孩子在自己身边&#xff0c;引得远方父母思念挂牵&#xff0c;时时刻刻与父母视频通话很不现实&#xff0c;老人也没法和自己的老伙伴们分…

酒店管理系统

登录模块设计 &#xff08;1&#xff09;在登录界面&#xff0c;包含用户名&#xff0c;密码信息&#xff0c;输入之后点击登录进入酒店管理系统。 &#xff08;2&#xff09;用户名和密码都不能为空&#xff0c;否则会弹出对话框进行提醒。 &#xff08;3&#xff09;后端也会…

口碑最好的运动蓝牙耳机推荐,2022年最值得入手的六款运动耳机

说到耳机&#xff0c;绝大多数人都在使用&#xff0c;对于运动爱好者就更不用多说&#xff0c;运动耳机随身携带着&#xff0c;跑道哪&#xff0c;戴到哪。随着耳机越来越多样化&#xff0c;各种耳机层出不穷&#xff0c;大家都难挑选&#xff0c;不知道怎么找&#xff0c;一副…

Aspose.PDF for .NET 22.11.0 Crack

Aspose.PDF for .NET 是一个本地库&#xff0c;使开发人员能够将 PDF 处理功能添加到他们的应用程序中。API 可用于构建任何类型的 32 位和 64 位应用程序&#xff0c;以在不使用 Adob​​e Acrobat 的情况下生成或读取、转换和操作 PDF 文件。 Aspose.PDF for .NET API 允许执…

红队渗透靶场之W1R3S靶场(超详细!)

W1R3S考察知识 nmap的基本使用目录爆破工具的使用CMS漏洞的利用Linux用户的简单提权 W1R3S靶场搭建 W1R3S靶场下载地址: https://download.vulnhub.com/w1r3s/w1r3s.v1.0.1.zip 将下载的虚拟机文件放到VMware中打开运行即可 渗透步骤 信息收集 1.存活主机发现 扫描C段存活…

CDMP认证考试您最关心的事——考试通过率

当今&#xff0c;数据要素化已然成为数字时代最重要的基础资源和战略资源。培养专业的数据治理人才&#xff0c;建立企业数据治理体系&#xff0c;提升数字化转型能力已成为当今社会企业发展的必然趋势。专门的数据管理专业认证考试并不多&#xff0c;主要就是由DAMA&#xff0…

day01 Linux

day01 Linux 第一章 Linux的概述 第一节 Linux是什么 Linux 英文解释为 Linux is not Unix Linux是Linus Torvolds于1991年开发的一套免费使用和自由传播的类Unix操作系统&#xff0c;是一个基于POSIX和UNIX的多用户、多任务、支持多线程和多CPU的操作系统。 (注&#xff…

MySQL排查篇:该如何定位并解决线上突发的Bug与疑难杂症?

前面两章中&#xff0c;聊到了关于数据库性能优化的话题&#xff0c;而本文则再来聊一聊关于MySQL线上排查方面的话题。线上排查、性能优化等内容是面试过程中的“常客”&#xff0c;而对于线上遇到的“疑难杂症”&#xff0c;需要通过理性的思维去分析问题、排查问题、定位问题…

【Flutter】shape 属性 ShapeBorder,形状

文章目录前言一、shape 是什么&#xff1f;二、不同的形状1.BeveledRectangleBorder2.Border3.CircleBorder圆形4.ContinuousRectangleBorder连续圆角5.StadiumBorder 体育场边界 &#xff0c;药丸形状6.OutlineInputBorder外边框可以定制圆角7.UnderlineInputBorder下划线总结…

卡特兰数、真二叉树、出栈序列、n对括号合法表达式

一、本文主要介绍一下几个问题 什么是卡特兰数n对括号组成的合法表达式个数与卡特兰数的关系真二叉树的形态总数与卡特兰数的关系n个互异元素出栈序列数与卡特兰数的关系 1、什么是卡特兰数 卡特兰数是指满足以下递推关系的数&#xff1a; 这个数跟斐波拉契数列一样是一个递归…

腾格尔刚刚参加线上演唱会,又传《巴林塔娜》合作主题曲身价倍增

俗话说&#xff1a;人逢喜事精神爽。没有想到音乐老前辈腾格尔&#xff0c;因为一场线上演唱会&#xff0c;又枯木逢春老树发新芽了。说起音乐人腾格尔&#xff0c;这位来自草原上的歌神&#xff0c;曾经有很多部草原歌曲&#xff0c;都给大家留下了非常深刻的印象。 在腾格尔老…