本章概要

  • 虚拟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 } = Vuereturn h('h' + this.level, // tag name{}, // props/attributesthis.$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 } = Vuereturn h('h' + this.level, // tag name{}, // props/attributesthis.$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/attributesthis.$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 风格的 IDconst 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',[// 错误 : 重复的 VNodemyVNode,myVNode])
}

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

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

十二、虚拟 DOM 和 render() 函数(1)相关推荐

  1. 虚拟机dhcp服务器怎么检验,实验十二虚拟机上DHCP服务器的配置和验证.doc

    实验十二 虚拟机上DHCP服务器的配置与验证 一.实验目的 了解DHCP的基本概念和服务器的新特性 掌握DHCP服务器的安装与配置 掌握DHCP的运行方式 掌握DHCP客户机的设置 掌握ipconfi ...

  2. 计算机基础函数运用,计算机应用基础第十二讲:EXCEL中函数的实际运用.doc

    文档介绍: 计算机应用基础第十二讲:EXCEL中函数的实际运用.doc计算机应用基础第十二讲:EXCEL中函数的实际运用课 题EXCEL屮函数的实际运用课型多媒体课授课时间第20周教学目的实例分析,掌 ...

  3. C语言编程>第二十二周 ⑥ 请补充fun函数,该函数的功能是:把字符下标能被2和3同时整除的字符从字符串s中删除,把剩余的字符重新保存在字符串s中。

    例题:请补充fun函数,该函数的功能是:把字符下标能被2和3同时整除的字符从字符串s中删除,把剩余的字符重新保存在字符串s中.字符串s从键盘输入,其长度作为参数传入fun函数. 例如,输入 " ...

  4. 轻松学习JavaScript二十二:DOM编程学习之节点操作

    DOM编程不仅仅可以查找三种节点,也可以操作节点,那就是创建,插入,删除,替换和复制节点.先来看节点 操作方法:        还是借用一贯的HTML代码: <!DOCTYPE html PUB ...

  5. 十二、Linux文件 - fseek函数讲解

    目录 一.fseek函数讲解 二.fseek函数实战 一.fseek函数讲解 重定向文件内部的指针 注:光标 ---- 文件内部的指针 函数原型: int fseek(FILE *stream,lon ...

  6. 零基础学Python(第二十二章 常用内置函数)

    本套学习内容共计[22]个章节,每个章节都会有对应的从0-1的学习过程详细讲解,希望可以给更多的人提供帮助. 开发环境:[Win10] 开发工具:[Visual Studio 2019] 本章内容为: ...

  7. 第十二讲 dom对象(DOM对象、document对象的常用方法、节点、查找结点、 查看/修改/删除属性节点、创建和增加节点)

    一.查看节点 getElementById( ) 元素的ID名称来访问,返回对拥有指定id的第一个对象的引用 getElementsByName( )  按元素的name名称来访问,返回带有指定名称的 ...

  8. 整型数组处理算法(十二)请实现一个函数:最长顺子。[风林火山]

    请实现一个函数:最长顺子:输入很多个整数(1<=数值<=13), 返回其中可能组成的最长的一个顺子(顺子中数的个数代表顺的长度): 其中数字1也可以代表14: 顺子包括单顺\双顺\3顺: ...

  9. 【叶子函数分享四十二】得到汉字笔画函数

    --=============================================== --功能:汉字笔画函数 --说明:以单个汉字汉字为参数返回每一个汉字的笔画数 --作者: J9988 ...

最新文章

  1. Android NDK JNI WARNING: illegal start byte 0x
  2. mac 查看端口_如何重置mac上的系统管理控制器smc教程
  3. 约瑟夫环c语言计蒜客链表,约瑟夫环的故事 - osc_3n35hvex的个人空间 - OSCHINA - 中文开源技术交流社区...
  4. 杭电oj1257最少拦截系统—贪心/dp最大递增子序列
  5. 仪表指针样式_Qt自定义Widget之仪表盘
  6. JavaScript学习总结(四)——逻辑OR运算符详解
  7. 数据分析入门:如何训练数据分析思维?
  8. 为什么高手离不了Linux系统?我想这就是理由!
  9. python2d 平滑插值处理_python中平滑的、通用的2D线性插值
  10. 要闻君说:特斯拉重磅推出影响力报告;三星官宣完成5纳米EUV工艺研发还承诺提供样品;国内首条5G智能制造生产线正式“上马”...
  11. codeforces 453C Little Pony and Summer Sun Celebration
  12. 上机演练 幸运抽奖活动
  13. sql2012 ssrs_您必须在SQL Server Reporting Services(SSRS)中记录的十件事
  14. Eclipse 4.10.0 正式发布,全面拥抱 Java 11!
  15. 第1章 游戏之乐——光影切割问题
  16. OUC_Summer Training_ DIV2_#11 722
  17. W3CSchool离线文档下载
  18. APICloud平台使用融云模块实现音视频通话实践经验总结分享
  19. 利用在线PS将一张图片上的中文改写成英文
  20. 切线空间?切线空间的作用到底是什么?

热门文章

  1. SPSS modeler 连接数据库clickhouse
  2. b站pink老师JavaScript的PC端网页特效 案例代码——仿淘宝固定侧边栏
  3. 查询最近电脑都访问过哪些文件
  4. spring3和spring4的一些需要注意的地方
  5. IoT黑板报:高通和LG携手于2018年开始测试5G车辆互联网
  6. 如何使用Stunnel和redis-cli通过TLS连接到托管Redis实例
  7. VSCode中npm包管理器安装到卸载
  8. docker(7、容器网络6) weave 网络 Weave 跨主机的连通和隔离特性
  9. 把一块钱换成1分2分5分的硬币,有多少种方法?
  10. Entity Framework 学习