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

news2024/10/6 10:43:07

目录

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 function
  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);
    }
    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.coloradmin.cn/o/1476784.html

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

相关文章

mongoDB 优化(1)索引

1、创建复合索引&#xff08;多字段&#xff09; db.collection_test1.createIndex({deletedVersion: 1,param: 1,qrYearMonth: 1},{name: "deletedVersion_1_param_1_qrYearMonth_1",background: true} ); 2、新增索引前&#xff1a; 执行查询&#xff1a; mb.r…

第3部分 原理篇2去中心化数字身份标识符(DID)(4)

3.2.3. DID解析 3.2.3.1. DID解析参与方 图3-5 DID 解析过程 本聪老师&#xff1a;我们之前提到过&#xff0c;DID 解析过程是将 DID 转换为对应的 DID 文档。这样做的目的是验证 DID 所代表的主体的身份。那么解析过程会涉及哪些概念呢&#xff1f;我们看图3-&#xff0c;DI…

uniapp 微信小程序使用高德地图Vue3不兼容Vue2问题

1. uniapp 微信小程序使用高德地图Vue3不兼容Vue2问题 1.1. 问题 uniapp Vue3项目引用高德地图报错 import amapPlugin from ‘…/…/…/js_sdk/js_amap/amap-wx.130’; "default" is not exported by "../../../MyProject/Base/Szy/js_sdk/js_amap/amap-wx.1…

springboot+vue网站开发-后端管理框架-vue-admin-template

为了方便国内用户下载&#xff0c;我把自己的百度网盘分享给大家一份地址&#xff0c;可以去下载。 如果你有上网盒子软件&#xff0c;那就自己去下载&#xff0c;很小。不到1MB. 链接&#xff1a;https://pan.baidu.com/s/15LJ2MoSWToFGFp28VaxBeQ?pwdbaby 提取码&#xff…

C++之queue和dqueue

1、queue queue&#xff08;队列&#xff09;&#xff0c;一种数据结构&#xff0c;可以让某些数据结构的操作变得简单。队列&#xff08;queue&#xff09;最大的特点就是先进先出。就是说先放入queue容器的元素一定是要先出队列之后&#xff0c;比它后进入队列的元素才能够出…

【MATLAB源码-第150期】基于matlab的开普勒优化算法(KOA)机器人栅格路径规划,输出做短路径图和适应度曲线。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 开普勒优化算法&#xff08;Kepler Optimization Algorithm, KOA&#xff09;是一个虚构的、灵感来自天文学的优化算法&#xff0c;它借鉴了开普勒行星运动定律的概念来设计。在这个构想中&#xff0c;算法模仿行星围绕太阳的…

图论(算法竞赛、蓝桥杯)--Dijkstra算法最短路

1、B站视频链接&#xff1a;D02 最短路 Dijkstra 算法_哔哩哔哩_bilibili 题目链接&#xff1a;【模板】单源最短路径&#xff08;弱化版&#xff09; - 洛谷 #include <bits/stdc.h> using namespace std; #define INF 2147483647 int n,m,s,a,b,c; const int N100010…

Spring Boot项目中如何上传头像?

在我们常见的各大App中&#xff0c;或多或少我们都见过上传头像的功能吧&#xff1f;&#xff1f; 但是在Spring Boot项目中如何上传头像呢&#xff1f; 上传头像主要用到RequestPart注解 来看一下小编的代码吧&#xff01; RestController RequestMapping("/param"…

ONLYOFFICE桌面编辑器v8.0完整指南:安装、特点与新增功能

文章目录 摘要引言安装主界面可填写的 PDF 表单双向文本支持电子表格中的新增功能其他改进与Moodle集成用密码保护PDF文件从“开始”菜单快速创建文档本地界面主题安装免费的 ONLYOFFICE桌面编辑器 总结 摘要 本文介绍了ONLYOFFICE桌面编辑器v8.0的安装、主界面特点以及新增功…

R语言实现分位数回归和二次分位数回归

大家好&#xff0c;我是带我去滑雪&#xff01;新的一年&#xff0c;新的气象&#xff0c;在接下来的日子里我将继续和各位小伙伴们分享我在科研道路上&#xff0c;学习的一些知识&#xff01; 分位数回归和二次分位数回归是统计学中用于分析因变量与自变量之间关系的方法&…

【InternLM 实战营笔记】浦语大模型趣味 Demo

大模型及 InternLM 模型简介 1.1 什么是大模型&#xff1f; 大模型通常指的是机器学习或人工智能领域中参数数量巨大、拥有庞大计算能力和参数规模的模型。这些模型利用大量数据进行训练&#xff0c;并且拥有数十亿甚至数千亿个参数。大模型的出现和发展得益于增长的数据量、…

s-table和columns初始化不完整,造成table文件的filter报错

问题 顺藤摸瓜找errorHandler.js文件 发现文件并没有什么问题 顺藤摸瓜找index.vue文件 首先找到报错的filter&#xff0c;发现与columnsSetting相关 找到columnsSetting发现等于columns 返回自己使用S-table组件的地方&#xff0c;发现columns初始化时仅初始化为ref()未表明…

微信小程序证书评级导致接口无法访问问题

微信小程序的ssl证书到期后&#xff0c; 更换了免费的ssl证书&#xff0c; 是在freessl网站申请的&#xff0c; 配置完了&#xff0c;后台可以访问https网页&#xff0c;但是小程序还是无法访问&#xff0c; 开始没有怀疑是https证书的问题&#xff0c; 调适了好长时间的代码&a…

揭示IP风险画像的作用与价值

在当今数字化时代&#xff0c;互联网的快速发展为企业和个人带来了巨大的机遇&#xff0c;同时也带来了各种安全风险和威胁。随着网络攻击手段的不断升级和演变&#xff0c;传统的安全防御手段已经无法满足对抗复杂多变的网络威胁的需求。IP风险画像作为一种新型的网络安全解决…

45、WEB攻防——通用漏洞PHP反序列化POP链构造魔术方法原生类

文章目录 序列化&#xff1a;将java、php等代码中的对象转化为数组或字符串等格式。代表函数serialize()&#xff0c;将一个对象转换成一个字符&#xff1b;反序列化&#xff1a;将数组或字符串等格式还成对象。代表函数unserialize()&#xff0c;将字符串还原成一个对象。 P…

亚马逊自养号测评:如何安全搭建环境,有效规避风险

要在亚马逊上进行自养号测评&#xff0c;构建一个真实的国外环境至关重要。这包括模拟国外的服务器、IP地址、浏览器环境&#xff0c;甚至支付方式&#xff0c;以创建一个完整的国际操作环境。这样的环境能让我们自由注册、养号并下单&#xff0c;确保所有操作均符合国际规范。…

【MySQL】MySQL5.7版本安装与配置

目录 官网下载配置环境变量添加自定义配置打开命令窗口执行安装命令执行初始化命令启动MySQL服务设置MySQL密码 官网 MySQ官网地址&#xff1a;https://www.mysql.com/ 下载 选择DOWNLOADS 拉到下面 选择MySQL Community Server 默认是8.0版本&#xff0c;选择Archives…

【论文阅读】Usenix Security 2023 你看不见我:对基于激光雷达的自动驾驶汽车驾驶框架的物理移除攻击

文章目录 一.论文信息二.论文内容1.摘要2.引言3.作者贡献4.主要图表5.结论 一.论文信息 论文题目&#xff1a; You Can’t See Me: Physical Removal Attacks on LiDAR-based Autonomous Vehicles Driving Frameworks&#xff08;你看不见我:对基于激光雷达的自动驾驶汽车驾驶…

重拾前端基础知识:JavaScript

重拾前端基础知识&#xff1a;JavaScript 前言使用JavaScript输出语法运算符条件语句循环数据类型字符串数字数组对象日期函数 数学正则表达式异常处理类集合模块JSON闭包异步调试DOM&#xff08;文档对象模型&#xff09;事件事件监听器表单 BOM&#xff08;浏览器对象模型&am…

嵌入式烧录报错:板端IP与PC的IP相同

报错&#xff1a; 配置 实际上我配置并没有错。 服务器IP&#xff08;就是本机&#xff09;、板端IP、网关。此处网关必须与板子IP配套&#xff08;可以不存在&#xff09;。 解决 我网卡配置了多个IP。一番删除添加还是报错。 于是点击服务器IP&#xff0c;换成别的&#x…