Vue源码系列讲解——生命周期篇【七】(模板编译阶段)

目录

1. 前言

2. 模板编译阶段分析

2.1 两种$mount方法对比

2.2 完整版的vm.$mount方法分析

3. 总结


1. 前言

前几篇文章中我们介绍了生命周期的初始化阶段,我们知道,在初始化阶段各项工作做完之后调用了vm.$mount方法,该方法的调用标志着初始化阶段的结束和进入下一个阶段,从官方文档给出的生命周期流程图中可以看到,下一个阶段就进入了模板编译阶段,该阶段所做的主要工作是获取到用户传入的模板内容并将其编译成渲染函数。

模板编译阶段并不是存在于Vue的所有构建版本中,它只存在于完整版(即vue.js)中。在只包含运行时版本(即vue.runtime.js)中并不存在该阶段,这是因为当使用vue-loadervueify时,*.vue文件内部的模板会在构建时预编译成渲染函数,所以是不需要编译的,从而不存在模板编译阶段,由上一步的初始化阶段直接进入下一阶段的挂载阶段。

在这里,我们有必要介绍一下什么是完整版和只包含运行时版。

vue基于源码构建的有两个版本,一个是runtime only(一个只包含运行时的版本),另一个是runtime + compiler(一个同时包含编译器和运行时的完整版本)。而两个版本的区别仅在于后者包含了一个编译器。

  • 完整版本

    一个完整的Vue版本是包含编译器的,我们可以使用template选项进行模板编写。编译器会自动将template选项中的模板字符串编译成渲染函数的代码,源码中就是render函数。如果你需要在客户端编译模板 (比如传入一个字符串给 template 选项,或挂载到一个元素上并以其 DOM 内部的 HTML 作为模板),就需要一个包含编译器的版本。 如下:

    // 需要编译器的版本
    new Vue({template: '<div>{{ hi }}</div>'
    })
    

  • 只包含运行时版本

    只包含运行时的版本拥有创建Vue实例、渲染并处理Virtual DOM等功能,基本上就是除去编译器外的完整代码。该版本的适用场景有两种:

    1.我们在选项中通过手写render函数去定义渲染过程,这个时候并不需要包含编译器的版本便可完整执行。

    // 不需要编译器
    new Vue({render (h) {return h('div', this.hi)}
    })
    

    2.借助vue-loader这样的编译工具进行编译,当我们利用webpack进行Vue的工程化开发时,常常会利用vue-loader*.vue文件进行编译,尽管我们也是利用template模板标签去书写代码,但是此时的Vue已经不需要利用编译器去负责模板的编译工作了,这个过程交给了插件去实现。

很明显,编译过程对性能会造成一定的损耗,并且由于加入了编译的流程代码,Vue代码的总体积也更加庞大(运行时版本相比完整版体积要小大约 30%)。因此在实际开发中,我们需要借助像webpackvue-loader这类工具进行编译,将Vue对模板的编译阶段合并到webpack的构建流程中,这样不仅减少了生产环境代码的体积,也大大提高了运行时的性能,一举两得。

为了完整的学习源码,本篇文章将会分析完整版中的模板编译阶段到底做了些什么。

2. 模板编译阶段分析

上文中说了,完整版和只包含运行时版之间的差异主要在于是否有模板编译阶段,而是否有模板编译阶段主要表现在vm.$mount方法的实现上。此时你可能会有疑问:照这么说,$mount方法也有两个版本?对的,你可以这么理解,但归根结底来说还是一种。我们分别来看一下。

2.1 两种$mount方法对比

只包含运行时版本的$mount代码如下:

Vue.prototype.$mount = function (el,hydrating) {el = el && inBrowser ? query(el) : undefined;return mountComponent(this, el, hydrating)
};

在该版本中的$mount方法内部获取到el选项对应的DOM元素后直接调用mountComponent函数进行挂载操作,关于该函数我们会在挂载阶段详细介绍。

而完整版本的$mount代码如下:

var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (el,hydrating) {// 省略获取模板及编译代码return mount.call(this, el, hydrating)
}

注意,在完整版本的$mount定义之前,先将Vue原型上的$mount方法先缓存起来,记作变量mount。此时你可能会问了,这$mount方法还没定义呢,怎么先缓存起来了。

其实在源码中,是先定义只包含运行时版本的$mount方法,再定义完整版本的$mount方法,所以此时缓存的mount变量就是只包含运行时版本的$mount方法。

为什么要这么做呢?上文我们说了,完整版本和只包含运行时版本之间的差异主要在于是否有模板编译阶段,只包含运行时版本没有模板编译阶段,初始化阶段完成后直接进入挂载阶段,而完整版本是初始化阶段完成后进入模板编译阶段,然后再进入挂载阶段。也就是说,这两个版本最终都会进入挂载阶段。所以在完整版本的$mount方法中将模板编译完成后需要回头去调只包含运行时版本的$mount方法以进入挂载阶段。

这也就是在完整版本的$mount方法中先把只包含运行时版本的$mount方法缓存下来,记作变量mount,然后等模板编译完成,再执行mount方法(即只包含运行时版本的$mount方法)。

所以分析模板编译阶段其实就是分析完整版的vm.$mount方法的实现。

2.2 完整版的vm.$mount方法分析

完整版的vm.$mount方法定义位于源码的dist/vue.js中,如下:

var mount = Vue.prototype.$mount;
Vue.prototype.$mount = function (el,hydrating) {el = el && query(el);if (el === document.body || el === document.documentElement) {warn("Do not mount Vue to <html> or <body> - mount to normal elements instead.");return this}var options = this.$options;// resolve template/el and convert to render functionif (!options.render) {var template = options.template;if (template) {if (typeof template === 'string') {if (template.charAt(0) === '#') {template = idToTemplate(template);/* istanbul ignore if */if (!template) {warn(("Template element not found or is empty: " + (options.template)),this);}}} else if (template.nodeType) {template = template.innerHTML;} else {{warn('invalid template option:' + template, this);}return this}} else if (el) {template = getOuterHTML(el);}if (template) {if (config.performance && mark) {mark('compile');}var ref = compileToFunctions(template, {outputSourceRange: "development" !== 'production',shouldDecodeNewlines: shouldDecodeNewlines,shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this);var render = ref.render;var staticRenderFns = ref.staticRenderFns;options.render = render;options.staticRenderFns = staticRenderFns;if (config.performance && mark) {mark('compile end');measure(("vue " + (this._name) + " compile"), 'compile', 'compile end');}}}return mount.call(this, el, hydrating)
};

从代码中可以看到,该函数可大致分为三部分:

  • 根据传入的el参数获取DOM元素;
  • 在用户没有手写render函数的情况下获取传入的模板template
  • 将获取到的template编译成render函数;

接下来我们就逐一分析。

首先,根据传入的el参数获取DOM元素。如下:

el = el && query(el);function query (el) {if (typeof el === 'string') {var selected = document.querySelector(el);if (!selected) {warn('Cannot find element: ' + el);return document.createElement('div')}return selected} else {return el}
}

由于el参数可以是元素,也可以是字符串类型的元素选择器,所以调用query函数来获取到el对应的DOM元素。由于query函数比较简单,就是根据传入的el参数是否为字符串从而以不同方式获取到对应的DOM元素,这里就不逐行展开介绍了。

另外,这里还多了一个判断,就是判断获取到el对应的DOM元素如果是bodyhtml元素时,将会抛出警告。这是因为Vue会将模板中的内容替换el对应的DOM元素,如果是bodyhtml元素时,替换之后将会破坏整个DOM文档,所以不允许elbodyhtml。如下:

if (el === document.body || el === document.documentElement) {warn("Do not mount Vue to <html> or <body> - mount to normal elements instead.");return this
}

接着,在用户没有手写render函数的情况下获取传入的模板template;如下:

if (!options.render) {var template = options.template;if (template) {if (typeof template === 'string') {if (template.charAt(0) === '#') {template = idToTemplate(template);/* istanbul ignore if */if (!template) {warn(("Template element not found or is empty: " + (options.template)),this);}}} else if (template.nodeType) {template = template.innerHTML;} else {{warn('invalid template option:' + template, this);}return this}} else if (el) {template = getOuterHTML(el);}
}

首先获取用户传入的template选项赋给变量template,如果变量template存在,则接着判断如果template是字符串并且以##开头,则认为templateid选择符,则调用idToTemplate函数获取到选择符对应的DOM元素的innerHTML作为模板,如下:

if (template) {if (typeof template === 'string') {if (template.charAt(0) === '#') {template = idToTemplate(template);}}
}var idToTemplate = cached(function (id) {var el = query(id);return el && el.innerHTML
});

如果template不是字符串,那就判断它是不是一个DOM元素,如果是,则使用该DOM元素的innerHTML作为模板,如下:

if (template.nodeType) {template = template.innerHTML;
}

如果既不是字符串,也不是DOM元素,此时会抛出警告:提示用户template选项无效。如下:

else {{warn('invalid template option:' + template, this);}return this
}

如果变量template不存在,表明用户没有传入template选项,则根据传入的el参数调用getOuterHTML函数获取外部模板,如下:

if (el) {template = getOuterHTML(el);
}function getOuterHTML (el) {if (el.outerHTML) {return el.outerHTML} else {var container = document.createElement('div');container.appendChild(el.cloneNode(true));return container.innerHTML}
}

不管是从内部的template选项中获取模板,还是从外部获取模板,总之就是要获取到用户传入的模板内容,有了模板内容接下来才能将模板编译成渲染函数。

获取到模板之后,接下来要做的事就是将其编译成渲染函数,如下:

if (template) {var ref = compileToFunctions(template, {outputSourceRange: "development" !== 'production',shouldDecodeNewlines: shouldDecodeNewlines,shouldDecodeNewlinesForHref: shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this);var render = ref.render;var staticRenderFns = ref.staticRenderFns;options.render = render;options.staticRenderFns = staticRenderFns;
}

关于将模板编译成渲染函数的具体步骤在前面文章模板编译篇中已经做了详细介绍,在这里,我们仅做简单回顾。

把模板编译成渲染函数是在compileToFunctions函数中进行的,该函数接收待编译的模板字符串和编译选项作为参数,返回一个对象,对象里面的render属性即是编译好的渲染函数,最后将渲染函数设置到$options上。

3. 总结

本篇文章介绍了生命周期中的第二个阶段——模板编译阶段。

首先介绍了Vue源码构建的两种版本:完整版本和只包含运行时版本。并且我们知道了模板编译阶段只存在于完整版中,在只包含运行时版本中不存在该阶段,这是因为在只包含运行时版本中,当使用vue-loadervueify时,*.vue文件内部的模板会在构建时预编译成渲染函数,所以是不需要编译的,从而不存在模板编译阶段。

然后对比了两种版本$mount方法的区别。它们的区别在于在$mount方法中是否进行了模板编译。在只包含运行时版本的$mount方法中获取到DOM元素后直接进入挂载阶段,而在完整版本的$mount方法中是先将模板进行编译,然后回过头调只包含运行时版本的$mount方法进入挂载阶段。

最后,我们知道了分析模板编译阶段其实就是分析完整版的vm.$mount方法的实现,我们将完整版的vm.$mount方法源码进行了逐行分析。知道了在该阶段中所做的工作就是:从用户传入的el选项和template选项中获取到用户传入的内部或外部模板,然后将获取到的模板编译成渲染函数。

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

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

相关文章

前后端分离PHP+vue+mysql城市轨道交通线路公交查询系统

医院、厕所、药店、派出所、学校、营业厅、快递、银行 开发语言&#xff1a;php 后端框架&#xff1a;Thinkphp 前端框架&#xff1a;vue.js 服务器&#xff1a;apache 数据库&#xff1a;mysql 运行环境:phpstudy/wamp/xammp等 A.美食 快餐、中餐、自助餐、火锅、烧烤、奶…

STM32 I2C学习

IIC总线协议介绍 IIC&#xff1a;Inter Integrated Circuit&#xff0c;集成电路总线&#xff0c;是一种同步、串行、半双工通信总线。 同步&#xff1a;需要时钟线 串行&#xff1a;数据一位一位地发送 半双工&#xff1a;同一时间只能接受或发送&#xff0c;不能同时发送或…

【Unity】如何从现有项目中抽取好用的资源

【背景】 在做Unity项目的过程中引入各种各样的Package&#xff0c;有的Package很大&#xff0c;但是觉得非常有用的可能只是几个Prefab或者Material等。如果直接拷贝想要的Prefab和Material&#xff0c;又需要自己确认所有有依赖关系的资源。 如果能将所有日常经受项目中自己…

SQLlabs46关

看看源码 最终我们的id是放到order by后面了 如果我们直接用列去排序 ?sortusername/password username&#xff1a; passward 可以看到顺序是不同的&#xff0c;当然第一列第二列第三列也可以&#xff0c;基本上都是这个原理&#xff0c;那怎么去实现注入呢&#xff0c;我…

杰理701N可视化SDK之组合键代码设计

杰理701N可视化SDK组合键代码设计 组合键相关代码修改组合键消息处理代码SDK加入组合键代码引出的问题 杰理701N可视化SDK目前只支持在可视化工具中配置按键和按键功能, 还不支持在可视化工具中直接加入组合键的功能. 需要在SDK中做些修改才可以实现组合键的功能. 本篇文章演示…

文本生成图像新SOTA!RealCompo:逼真和构图的动态平衡(清北最新)

文章链接&#xff1a;https://arxiv.org/pdf/2402.12908 最近AI生成内容领域取得了令人激动的很多成果&#xff0c;比如Sora、StableDiffusion-3等等。今天给大家分享另一个内容生成领域的SOTA模型——RealCompo&#xff0c;这是一种新的文本到图像生成框架&#xff0c;旨在利…

鸿蒙开发实战-手写一个Openharmony投屏工具

实战手写一个Openharmony投屏工具&#xff0c;实现代码分享如下&#xff1a; java import javax.imageio.ImageIO; import javax.swing.*; import java.awt.*; import java.awt.event.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOExcepti…

备战蓝桥杯————递归反转单链表的一部分

递归反转单链表已经明白了&#xff0c;递归反转单链表的一部分你知道怎么做吗&#xff1f; 一、反转链表Ⅱ 题目描述 给你单链表的头指针 head 和两个整数 left 和 right &#xff0c;其中 left < right 。请你反转从位置 left 到位置 right 的链表节点&#xff0c;返回 反…

Scikit-Learn逻辑回归

Scikit-Learn逻辑回归 1、逻辑回归概述1.1、逻辑回归1.2、逻辑回归的优缺点1.3、逻辑回归与线性回归 2、逻辑回归的原理2.1、逻辑回归的概念与原理2.2、逻辑回归的损失函数 3、 1、逻辑回归概述 1.1、逻辑回归 逻辑回归&#xff08;Logistic Regression&#xff09;主要解决二…

数字化的未来:构建全场景可信互联生态

本文摘自&#xff1a;思二勋所著、清华社出版的《分布式商业生态战略》一书。 随着万物互联(Internet of Everything&#xff0c;IoE)时代的到来&#xff0c;场景变成了互联的起点&#xff0c;场景内容变得更加丰富起来。如今&#xff0c;消费者的选择决策因素主要是基于场景的…

好书推荐丨AI绘画全面精通:软件平台+脚本文案+设计制作+案例实战

文章目录 写在前面AI绘画推荐图书图书简介本书特色作者简介 推荐理由粉丝福利写在后面 写在前面 本期博主给大家带来了一本全新出版的AI绘画类书籍&#xff0c;《AI绘画全面精通&#xff1a;软件平台脚本文案设计制作案例实战》&#xff0c;对人工智能感兴趣的小伙伴快来看看吧…

数学建模入门必看|关于2024第九届数维杯数学建模,你想知道的都在这里!

数维杯大学生数学建模挑战赛每年分为两场&#xff0c;每年上半年为数维杯国赛&#xff08;5月&#xff0c;俗称小国赛&#xff09;&#xff0c;下半年为数维杯国际赛(11月)&#xff0c;2023年第八届数维杯大学生数学建模挑战赛共有近1.4万名学生参赛&#xff0c;参赛队伍来自国…

Sora的潜力与影响:对视频制作、广告、娱乐等行业的深度解析

随着技术的飞速发展&#xff0c;OpenAI推出的Sora模型已经引起了广泛关注。作为一款强大的视频生成工具&#xff0c;Sora不仅改变了视频制作的传统模式&#xff0c;更对广告、娱乐等多个行业产生了深远影响。本文将深度解析Sora的潜力与影响&#xff0c;探讨其在视频制作、广告…

初学学习408之数据结构--数据结构基本概念

初学学习408之数据结构我们先来了解一下数据结构的基本概念。 数据结构&#xff1a;是相互之间存在一种或多种特定关系的数据元素的集合。 本内容来源于参考书籍《大话数据结构》与《王道数据结构》。除去书籍中的内容&#xff0c;作为初学者的我会尽力详细直白地介绍数据结构的…

面试总结之JVM入门

文章目录 &#x1f412;个人主页&#x1f3c5;JavaEE系列专栏&#x1f4d6;前言&#xff1a;&#x1f380;你为什么要学习JVM&#xff1f;&#x1f380;JVM的作用 &#x1f380;JVM的构成&#xff08;5大类&#xff09;&#x1f3e8;1.类加载系统&#x1f415;类什么时候会被加…

Vue3快速上手(十一)标签里的ref属性

一、介绍 标签里的ref&#xff0c;可以在普通html元素标签上&#xff0c;也可以用在vue组件标签上。用在html元素标签上时&#xff0c;返回的是该html元素的DOM&#xff1b;用在vue组件标签上时&#xff0c;返回的是该子组件&#xff08;也就是当前vue是父组件&#xff09;暴露…

MySQL篇—持久化和非持久化统计信息介绍(第一篇,总共三篇)

☘️博主介绍☘️&#xff1a; ✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ ✌✌️擅长Oracle、MySQL、SQLserver、Linux&#xff0c;也在积极的扩展IT方向的其他知识面✌✌️ ❣️❣️❣️大佬们都喜欢静静的看文章&#xff0c;并且也会默默的点赞收藏加关注❣…

springboot+vue实现微信公众号扫码登录

通常在个人网站中&#xff0c;都会有各种第三方登录&#xff0c;其中微信登录需要认证才能使用&#xff0c;导致个人开发者不能进行使用此功能&#xff0c;但是我们可以使用微信公众号回复特定验证码来进行登录操作。 微信关键词处理 微信公众号关键词自动回复&#xff0c;具体…

Vue3前端实现一个本地消息队列(MQ), 让消息延迟消费或者做缓存

MQ功能实现的具体代码(TsMQ.ts)&#xff1a; import { v4 as uuidx } from uuid;import emitter from /utils/mitt// 消息类 class Message {// 过期时间&#xff0c;0表示马上就消费exp: number;// 消费标识&#xff0c;避免重复消费tag : string;// 消息体body : any;constr…

数据结构与算法|线性结构

数据结构与算法|线性结构 第二章 线性结构2.1 多项式表示2.2 什么是线性表2.3 线性表的实现方式2.3.1 线性表的顺序存储实现2.3.2 线性表的链式存储实现1. 单链表实现2. 双链表实现 上篇&#xff1a;第一章、绪论 第二章 线性结构 线性结构是数据结构中最基础的&#xff0c;也…