Vue源码系列讲解——模板编译篇【五】(优化阶段)

1. 前言

在前几篇文章中,我们介绍了模板编译流程三大阶段中的第一阶段模板解析阶段,在这一阶段主要做的工作是用解析器将用户所写的模板字符串解析成AST抽象语法树,理论上来讲,有了AST就可直接进入第三阶段生成render函数了。其实不然,Vue还是很看重性能的,只要有一点可以优化的地方就要将其进行优化。在之前介绍虚拟DOM的时候我们说过,有一种节点一旦首次渲染上了之后不管状态再怎么变化它都不会变了,这种节点叫做静态节点,如下:

<ul><li>我是文本信息</li><li>我是文本信息</li><li>我是文本信息</li><li>我是文本信息</li><li>我是文本信息</li>
</ul>

在上面代码中,ul标签下面有5个li标签,每个li标签里的内容都是不含任何变量的纯文本,也就是说这种标签一旦第一次被渲染成DOM节点以后,之后不管状态再怎么变化它都不会变了,我们把像li的这种节点称之为静态节点。而这5个li节点的父节点是ul节点,也就是说ul节点的所有子节点都是静态节点,那么我们把像ul的这种节点称之为静态根节点。

OK,有了静态节点和静态根节点这两个概念之后,我们再仔细思考,模板编译的最终目的是用模板生成一个render函数,而用render函数就可以生成与模板对应的VNode,之后再进行patch算法,最后完成视图渲染。这中间的patch算法又是用来对比新旧VNode之间存在的差异。在上面我们还说了,静态节点不管状态怎么变化它是不会变的,基于此,那我们就可以在patch过程中不用去对比这些静态节点了,这样不就又可以提高一些性能了吗?

所以我们在模板编译的时候就先找出模板中所有的静态节点和静态根节点,然后给它们打上标记,用于告诉后面patch过程打了标记的这些节点是不需要对比的,你只要把它们克隆一份去用就好啦。这就是优化阶段存在的意义。

上面也说了,优化阶段其实就干了两件事:

  1. AST中找出所有静态节点并打上标记;
  2. AST中找出所有静态根节点并打上标记;

优化阶段的源码位于src/compiler/optimizer.js中,如下:

export function optimize (root: ?ASTElement, options: CompilerOptions) {if (!root) returnisStaticKey = genStaticKeysCached(options.staticKeys || '')isPlatformReservedTag = options.isReservedTag || no// 标记静态节点markStatic(root)// 标记静态根节点markStaticRoots(root, false)
}

接下来,我们就对所干的这两件事逐个分析。

2. 标记静态节点

AST中找出所有静态节点并标记其实不难,我们只需从根节点开始,先标记根节点是否为静态节点,然后看根节点如果是元素节点,那么就去向下递归它的子节点,子节点如果还有子节点那就继续向下递归,直到标记完所有节点。代码如下:

function markStatic (node: ASTNode) {node.static = isStatic(node)if (node.type === 1) {// do not make component slot content static. this avoids// 1. components not able to mutate slot nodes// 2. static slot content fails for hot-reloadingif (!isPlatformReservedTag(node.tag) &&node.tag !== 'slot' &&node.attrsMap['inline-template'] == null) {return}for (let i = 0, l = node.children.length; i < l; i++) {const child = node.children[i]markStatic(child)if (!child.static) {node.static = false}}if (node.ifConditions) {for (let i = 1, l = node.ifConditions.length; i < l; i++) {const block = node.ifConditions[i].blockmarkStatic(block)if (!block.static) {node.static = false}}}}
}

在上面代码中,首先调用isStatic函数标记节点是否为静态节点,该函数若返回true表示该节点是静态节点,若返回false表示该节点不是静态节点,函数实现如下:

function isStatic (node: ASTNode): boolean {if (node.type === 2) { // 包含变量的动态文本节点return false}if (node.type === 3) { // 不包含变量的纯文本节点return true}return !!(node.pre || (!node.hasBindings && // no dynamic bindings!node.if && !node.for && // not v-if or v-for or v-else!isBuiltInTag(node.tag) && // not a built-inisPlatformReservedTag(node.tag) && // not a component!isDirectChildOfTemplateFor(node) &&Object.keys(node).every(isStaticKey)))
}

该函数的实现过程其实也说明了如何判断一个节点是否为静态节点。还记得在HTML解析器在调用钩子函数创建AST节点时会根据节点类型的不同为节点加上不同的type属性,用type属性来标记AST节点的节点类型,其对应关系如下:

type取值对应的AST节点类型
1元素节点
2包含变量的动态文本节点
3不包含变量的纯文本节点

所以在判断一个节点是否为静态节点时首先会根据type值判断节点类型,如果type值为2,那么该节点是包含变量的动态文本节点,它就肯定不是静态节点,返回false

if (node.type === 2) { // 包含变量的动态文本节点return false
}

如果type值为2,那么该节点是不包含变量的纯文本节点,它就肯定是静态节点,返回true

if (node.type === 3) { // 不包含变量的纯文本节点return true
}

如果type值为1,说明该节点是元素节点,那就需要进一步判断。

node.pre ||
(!node.hasBindings && // no dynamic bindings!node.if && !node.for && // not v-if or v-for or v-else!isBuiltInTag(node.tag) && // not a built-inisPlatformReservedTag(node.tag) && // not a component!isDirectChildOfTemplateFor(node) &&Object.keys(node).every(isStaticKey)
)

如果元素节点是静态节点,那就必须满足以下几点要求:

  • 如果节点使用了v-pre指令,那就断定它是静态节点;
  • 如果节点没有使用v-pre指令,那它要成为静态节点必须满足:
    • 不能使用动态绑定语法,即标签上不能有v-@:开头的属性;
    • 不能使用v-ifv-elsev-for指令;
    • 不能是内置组件,即标签名不能是slotcomponent
    • 标签名必须是平台保留标签,即不能是组件;
    • 当前节点的父节点不能是带有 v-for 的 template 标签;
    • 节点的所有属性的 key 都必须是静态节点才有的 key,注:静态节点的key是有限的,它只能是type,tag,attrsList,attrsMap,plain,parent,children,attrs之一;

标记完当前节点是否为静态节点之后,如果该节点是元素节点,那么还要继续去递归判断它的子节点,如下:

for (let i = 0, l = node.children.length; i < l; i++) {const child = node.children[i]markStatic(child)if (!child.static) {node.static = false}
}

注意,在上面代码中,新增了一个判断:

if (!child.static) {node.static = false
}

这个判断的意思是如果当前节点的子节点有一个不是静态节点,那就把当前节点也标记为非静态节点。为什么要这么做呢?这是因为我们在判断的时候是从上往下判断的,也就是说先判断当前节点,再判断当前节点的子节点,如果当前节点在一开始被标记为了静态节点,但是通过判断子节点的时候发现有一个子节点却不是静态节点,这就有问题了,我们之前说过一旦标记为静态节点,就说明这个节点首次渲染之后不会再发生任何变化,但是它的一个子节点却又是可以变化的,就出现了自相矛盾,所以我们需要当发现它的子节点中有一个不是静态节点的时候,就得把当前节点重新设置为非静态节点。

循环node.children后还不算把所有子节点都遍历完,因为如果当前节点的子节点中有标签带有v-ifv-else-ifv-else等指令时,这些子节点在每次渲染时都只渲染一个,所以其余没有被渲染的肯定不在node.children中,而是存在于node.ifConditions,所以我们还要把node.ifConditions循环一遍,如下:

if (node.ifConditions) {for (let i = 1, l = node.ifConditions.length; i < l; i++) {const block = node.ifConditions[i].blockmarkStatic(block)if (!block.static) {node.static = false}}
}

同理,如果当前节点的node.ifConditions中有一个子节点不是静态节点也要将当前节点设置为非静态节点。

以上就是标记静态节点的全部逻辑。

3. 标记静态根节点

寻找静态根节点根寻找静态节点的逻辑类似,都是从AST根节点递归向下遍历寻找,其代码如下:

function markStaticRoots (node: ASTNode, isInFor: boolean) {if (node.type === 1) {if (node.static || node.once) {node.staticInFor = isInFor}// For a node to qualify as a static root, it should have children that// are not just static text. Otherwise the cost of hoisting out will// outweigh the benefits and it's better off to just always render it fresh.if (node.static && node.children.length && !(node.children.length === 1 &&node.children[0].type === 3)) {node.staticRoot = truereturn} else {node.staticRoot = false}if (node.children) {for (let i = 0, l = node.children.length; i < l; i++) {markStaticRoots(node.children[i], isInFor || !!node.for)}}if (node.ifConditions) {for (let i = 1, l = node.ifConditions.length; i < l; i++) {markStaticRoots(node.ifConditions[i].block, isInFor)}}}
}

上面代码中,首先markStaticRoots 第二个参数是 isInFor,对于已经是 static 的节点或者是 v-once 指令的节点,node.staticInFor = isInFor,如下:

if (node.static || node.once) {node.staticInFor = isInFor
}

接着判断该节点是否为静态根节点,如下:

// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
// 为了使节点有资格作为静态根节点,它应具有不只是静态文本的子节点。 否则,优化的成本将超过收益,最好始终将其更新。
if (node.static && node.children.length && !(node.children.length === 1 &&node.children[0].type === 3
)) {node.staticRoot = truereturn
} else {node.staticRoot = false
}

从代码和注释中我们可以看到,一个节点要想成为静态根节点,它必须满足以下要求:

  • 节点本身必须是静态节点;
  • 必须拥有子节点 children
  • 子节点不能只是只有一个文本节点;

否则的话,对它的优化成本将大于优化后带来的收益。

如果当前节点不是静态根节点,那就继续递归遍历它的子节点node.childrennode.ifConditions,如下:

if (node.children) {for (let i = 0, l = node.children.length; i < l; i++) {markStaticRoots(node.children[i], isInFor || !!node.for)}
}
if (node.ifConditions) {for (let i = 1, l = node.ifConditions.length; i < l; i++) {markStaticRoots(node.ifConditions[i].block, isInFor)}
}

这里的原理跟寻找静态节点相同,此处就不再重复。

4. 总结

本篇文章介绍了模板编译过程三大阶段的第二阶段——优化阶段。

首先,介绍了为什么要有优化阶段,是为了提高虚拟DOMpatch过程的性能。在优化阶段将所有静态节点都打上标记,这样在patch过程中就可以跳过对比这些节点。

接着,介绍了优化阶段主要干了两件事情,分别是从构建出的AST中找出并标记所有静态节点和所有静态根节点。

最后,分别通过逐行分析源码的方式分析了这两件事具体的内部工作原理。

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

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

相关文章

uniapp 开发一个密码管理app

密码管理app 介绍 最近发现自己的账号密码真的是太多了&#xff0c;各种网站&#xff0c;系统&#xff0c;公司内网的&#xff0c;很多站点在登陆的时候都要重新设置密码或者通过短信或者邮箱重新设置密码&#xff0c;真的很麻烦 所以准备开发一个app用来记录这些站好和密码…

软件风险分类整理

软件项目风险分类整理 1.需求分析 2.软件设计 3.编码和单元测试 4.集成和测试 5.验收和维护 6.团队管理 7.成本管理 8.组织管理

事务及在SpringBoot项目中使用的两种方式

1.事务简介 事务&#xff08;transaction&#xff09;是访问并可能操作各种数据项的一个数据库操作序列&#xff0c;这些操作要么全部执行&#xff0c;要么全部不执行&#xff0c;是一个不可分割的工作单位。 事物的四大特性: 原子性&#xff08;Atomicity&#xff09;&#xf…

ubuntu22.04@laptop OpenCV Get Started: 010_blob_detection

ubuntu22.04laptop OpenCV Get Started: 010_blob_detection 1. 源由2. blob应用Demo2.1 C应用Demo2.2 Python应用Demo 3. 重点分析3.1 Threshold3.2 Area3.3 Circularity3.4 Convexity3.5 Inertia Ratio 4. 总结5. 参考资料6. 补充 1. 源由 Blob是图像中的一组连接像素&#…

Go教程-Go语言简介

这篇文章我们来聊聊Go语言。 Go语言发展历史 以下是Go语言发展的几个里程碑节点&#xff1a; Go一开始是Google内部的一个项目&#xff0c;由三位大佬Rob Pike、Robert Griesemer、Ken Thompson早2007年发起。在2009年11月&#xff0c;Go语言正式对外开源。在2010年&#xf…

Java中的String类的常用方法(对于字符串的常用操作)

目录 一、获取指定索引的字符 二、 获取指定字符或者字符串的索引位置 三、判断字符串是否以指定内容开头或结尾 四、替换指定的字符或者是字符串 五、获取字符串的子串 六、将字符串转换为字符数组 七、比较字符串的内容是否相等 八、连接字符串 九、比较两个字符串的大…

2024-02-16 AIGC-数字人-硅基DUIX-记录

摘要: 2024-02-16 AIGC-数字人-硅基DUIX-记录 文档: https://duix.guiji.ai/duix-website/localHuman Digital Humans (guiji.ai) 文档中心 (guiji.ai) DUIX本地渲染SDK安卓集成文档_v1 | 文档中心 (guiji.ai) 本地化部署duix说明: 将DUIX离线部署到本地并在本地生成数字人需要…

ESP32学习(3)——连接WIFI

1.简介 Wi-Fi是基于IEEE 802.11标准的无线网络技术 让联网设备以无线电波的形式&#xff0c;加入采用TCP/IP通信协议的网络. Wi-Fi设备有两种模式&#xff1a; 1.Access Point(AP) 模式&#xff0c;此为无线接入点&#xff0c;家里的光猫就是结合WiFi和internet路由功能的AP。…

【Java程序员面试专栏 分布式中间件】Redis 核心面试指引

关于Redis部分的核心知识进行一网打尽,包括Redis的基本概念,基本架构,工作流程,存储机制等,通过一篇文章串联面试重点,并且帮助加强日常基础知识的理解,全局思维导图如下所示 基础概念 明确redis的特性、应用场景和数据结构 什么是Redis,Redis有哪些应用场景 Redi…

【十九】【C++】 priority_queue简单使用和仿函数

priority_queue文档介绍翻译 优先队列是一种容器适配器&#xff0c;专门设计成其中的第一个元素始终是根据某种严格的弱排序准则最大的元素。 这种上下文类似于堆&#xff0c;其中元素可以在任何时刻插入&#xff0c;而只能检索最大堆元素&#xff08;在优先队列中顶部的元素&a…

Stable Diffusion系列(五):原理剖析——从文字到图片的神奇魔法(扩散篇)

文章目录 DDPM论文整体原理前向扩散过程反向扩散过程模型训练过程模型生成过程概率分布视角参数模型设置论文结果分析 要想完成SD中从文字到图片的操作&#xff0c;必须要做到两步&#xff0c;第一步是理解文字输入包含的语义&#xff0c;第二步是利用语义引导图片的生成。下面…

【Java基础题型】力扣88、合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2&#xff0c;另有两个整数 m 和 n &#xff0c;分别表示 nums1 和 nums2 中的元素数目。 请你 合并 nums2 到 nums1 (要合并到的目标数组)中&#xff0c;使合并后的数组同样按 非递减顺序 排列。 注意&#xff1a;最终&a…

使用redis实现手机短信验证码和lua完成重置功能

文章目录 前言一、介绍二、代码1.LoginController2.reset.lua 总结 前言 2024了,各种各样的门户网站和APP都需要登录,登录方式也各种各样,由于都要绑定用户手机号码,所以大部分都支持了手机验证码登录的方式,接下来我们使用redis来完成验证码的功能。 一、介绍 方法名描述get…

深度学习入门-第2章-感知机

感知机&#xff08;perceptron&#xff09; 严格地讲&#xff0c;本章中所说的感知机应该称为“人工神经元”或“朴素感知机”&#xff0c;但是因为很多基本的处理都是共通的&#xff0c;所以这里就简单地称为“感知机”。2.1 感知机是什么 感知机接收多个输入信号&#xff0c…

Uipath 实现Excel 文件合并

场景描述 某文件夹下有多个相同结构(标题列相同)的Excel 文件&#xff0c;需实现汇总到一个Excel文件。 常见场景有销售明细汇总&#xff0c;订单汇总等。 解决方案 对于非IT 人员则可使用Uipath 新式Excel活动&#xff0c;通过拖拉实现。也可以通过内存表或使用VB脚本&…

解锁机器学习多类分类之门:Softmax函数的全面指南

1. 引言 Softmax函数的定义和基本概念 Softmax函数&#xff0c;也称为归一化指数函数&#xff0c;是一个将向量映射到另一个向量的函数&#xff0c;其中输出向量的元素值代表了一个概率分布。在机器学习中&#xff0c;特别是在处理多类分类问题时&#xff0c;Softmax函数扮演…

寒假学习记录17:包管理器(包管理工具)

概念 包&#xff08;package&#xff09; 包含元数据的库&#xff0c;这些元数据包括&#xff1a;名称&#xff0c;描述&#xff0c;git主页&#xff0c;许可证协议&#xff0c;作者&#xff0c;依赖..... 库&#xff08;library&#xff0c;简称lib&#xff09; 以一个或多个模…

第11讲投票创建后端实现

投票创建页面实现 文件选择上传组件 uni-file-picker 扩展组件 安装 https://ext.dcloud.net.cn/plugin?nameuni-file-picker 日期选择器uni-datetime-picker组件 安装 https://ext.dcloud.net.cn/plugin?nameuni-datetime-picker iconfont小图标 https://www.iconfont…

【教3妹学编程-算法题】子集中元素的最大数量

2哥 : 3妹&#xff0c;今年过年收到压岁钱了没呢。 3妹&#xff1a;切&#xff0c;我都多大了啊&#xff0c;肯定没收了啊 2哥 : 俺也一样&#xff0c;不仅没收到&#xff0c;小侄子小外甥都得给&#xff0c;还倒贴好几千 3妹&#xff1a;哈哈哈哈&#xff0c;2叔叔&#xff0c…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Navigation组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Navigation组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Navigation组件 鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#…