【Vue2.0源码学习】生命周期篇-模板编译阶段(template)

文章目录

    • 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方法定义位于源码的src/platforms/web/entry-runtime-with-compiler.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://www.mzph.cn/news/87063.shtml

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

相关文章

【跟小嘉学习区块链】二、Hyperledger Fabric 架构详解

系列文章目录 【跟小嘉学习区块链】一、区块链基础知识与关键技术解析 【跟小嘉学习区块链】一、区块链基础知识与关键技术解析 文章目录 系列文章目录[TOC](文章目录) 前言一、Hyperledger 社区1.1、Hyperledger(面向企业的分布式账本)1.2、Hyperledger社区组织结构 二、Hype…

UML六大关系总结

UML六大关系有&#xff1a;继承、关系、聚合、组合、实现、依赖。分为通过图和代码总结这些关系。 1、继承 继承&#xff08;Inheritance&#xff09;&#xff1a;表示类之间的继承关系&#xff0c;子类继承父类的属性和方法&#xff0c;并可以添加自己的扩展。 继承&#x…

【Spring Boot】Spring Boot源码解读与原理剖析

这里写目录标题 前言精进Spring Boot首选读物“小册”变“大书”&#xff0c;彻底弄懂Spring Boot全方位配套资源&#xff0c;学不会来找我&#xff01;技术新赛道&#xff0c;2023领先抢跑 前言 承载着作者的厚望&#xff0c;掘金爆火小册同名读物《Spring Boot源码解读与原理…

Windows11 手把手教授开放端口

首先在控制面板点击“系统与安全”&#xff0c;找到防火墙 然后点击“windows defender”打开防火墙 点击左侧目录栏中“高级设置” 点击“入站规则”&#xff0c;再点击新建入站规则&#xff08;开放端口有开放入站端口与开放出站端口之分&#xff0c;这里讲入站端口的开放…

老胡的周刊(第109期)

老胡的信息周刊[1]&#xff0c;记录这周我看到的有价值的信息&#xff0c;主要针对计算机领域&#xff0c;内容主题极大程度被我个人喜好主导。这个项目核心目的在于记录让自己有印象的信息做一个留存以及共享。 &#x1f3af; 项目 lobe-chat[2] LobeChat 是一个开源的、可扩展…

Redis 缓存雪崩、缓存穿透、缓存击穿

Redis 是一种常用的内存缓存工具&#xff0c;但在某些情况下&#xff0c;它可能会遭受缓存雪崩、缓存穿透和缓存击穿等问题。下面是一些预防这些问题的建议&#xff1a; 1、缓存雪崩 缓存雪崩指的是在某个时间点上&#xff0c;大量的缓存数据同时失效或过期&#xff0c;导致大…

华为云云耀云服务器L实例评测|华为云上安装kafka

文章目录 华为云云耀云服务器L实例评测&#xff5c;华为云上安装kafka一、kafka介绍二、华为云主机准备三、kafka安装1. 安装什么版本java2. 安装zookeeper服务3. 使用systemctl 管理启动ZooKeeper服务4. 修改kafka配置5. 使用systemctl 管理启动kafka服务6. 创建一个测试 topi…

前端JavaScript中的 == 和 ===区别,以及他们的应用场景,快来看看吧,积累一点知识。

&#x1f3ac; 江城开朗的豌豆&#xff1a;个人主页 &#x1f525; 个人专栏 :《 VUE 》 《 javaScript 》 ⛺️ 生活的理想&#xff0c;就是为了理想的生活 ! 目录 一、等于操作符 二、全等操作符 三、区别 小结 一、等于操作符 等于操作符用两个等于号&#xff08; &am…

map的一些测试-string键的查找

主要区别在于声明map的时候多了一个less<> #define _CRT_SECURE_NO_WARNINGS #include <iostream> #include <string> #include <map> #include <chrono> using namespace std; class spender { public:spender(string strfun) :strfun(strfun…

SAP 操作:怎么设定屏幕前台字段显示/编辑

文章目录 前言一、步骤设定方式 前言 SAP将字段放进群组&#xff0c;通过对群组进行控制。 一、步骤 后勤常规-物料主数据-字段选择 设定方式 点击后面绿色按钮2.

WPF 类库 使用handycontrol 配置

在学习wpf发现了一个非常好用的UI库 handycontrol 但是很多地方讲的都是WPF应用程序怎么用&#xff0c;很少有讲类库那么引用的问题&#xff0c;所以在这里自己总结一下&#xff0c;希望能帮助到大家&#xff1a; 1.添加 handycontrol 的引用&#xff1b;安装&#xff0c;我已…

前端面试题记录

vue2响应式原理 vue2主要是采用了数据劫持结合发布者-订阅者模式来实现数据的响应式&#xff0c;vue在初始化的时候&#xff0c;会遍历data中的数据&#xff0c;使用object.defineProperty为data中的每一个数据绑定setter和getter&#xff0c;当获取数据的时候会触发getter&am…

“构建完善的用户认证与数据交互系统“

目录 引言1.ElementUI完成登录注册1. 登录页面设计与实现2. 注册页面设计与实现 2.axios之get请求3.axios之post请求4.跨域问题的解决方案5.总结 引言 在现代Web应用程序开发中&#xff0c;用户认证和数据交互是至关重要的功能。本文将介绍如何使用ElementUI、axios和解决跨域…

C语言每日一题(8):有序序列合并

文章主题&#xff1a;有序序列合并&#x1f525;&#x1f525;&#x1f525;所属专栏&#xff1a;C语言每日一题&#x1f4d7;作者简介&#xff1a;每天不定时更新C语言的小白一枚&#xff0c;记录分享自己每天的所思所想&#x1f604;&#x1f3b6;个人主页&#xff1a;[₽]的…

Linux的socket通信

关于套接字通信定义如下&#xff1a; 套接字对应程序猿来说就是一套网络通信的接口&#xff0c;使用这套接口就可以完成网络通信。网络通信的主体主要分为两部分&#xff1a;客户端和服务器端。在客户端和服务器通信的时候需要频繁提到三个概念&#xff1a;IP、端口、通信数据&…

排序:希尔排序(Shell Sort)算法分析

1.算法思想 希尔排序:先追求表中元素部分有序再逐渐逼近全局有序. 希尔排序∶先将待排序表分割成若干形如 L [ i , i d , i 2 d . . . . , i k d ] L[i,i d,i 2d ...., i kd] L[i,id,i2d....,ikd]的“特殊”子表&#xff0c; 对各个子表分别进行直接插入排序。缩小增量…

MySQL单表查询和多表查询

一、单表查询 素材&#xff1a; 表名&#xff1a;worker-- 表中字段均为中文&#xff0c;比如 部门号 工资 职工号 参加工作等 CREATE TABLE worker (部门号 int(11) NOT NULL,职工号 int(11) NOT NULL,工作时间 date NOT NULL,工资 float(8,2) NOT NULL,政治面貌 varchar(10)…

恒合仓库 - 采购单管理模块

采购单管理模块 文章目录 采购单管理模块一、添加采购单(核心)1.1 采购流程1.2 采购单实体类1.3 添加采购单1.3.1 Mapper1.3.2 Service1.3.3 Controller1.3.4 效果图 二、采购单管理模块2.1 仓库数据回显2.1.1 Mapper2.1.2 Service2.1.3 Controller2.1.4 效果图 2.2 采购单列表…

【Unity的HDRP渲染管线搭建配置VR交互场景_SteamVR 插件和Pico串流助手】

HDRP渲染管线配置VR交互场景 Unity创建场景和相关配置下载导入项目打开PICO串流助手在Pico中的配置:用Steam串流VR_这篇的前置补充 Unity创建场景和相关配置 带HDRP Sample Scene 示例的 下载 SteamVR Unity插件地址02 导入项目