Vue源码剖析-模板编译

news/2024/7/10 2:05:26 标签: vue

Vue源码剖析-模板编译

  • 模板编译简介
  • 模板编译的结果
  • Vue Template Explorer
  • 编译的入口函数
    • createCompilerCreator函数详解
  • 模板编译的过程
    • compile函数
    • baseCompile-AST
    • baseCompile-parse
    • baseCompile-optimize
    • baseCompile-generate 上
    • 调试
      • 实例
      • 模板编译过程思维导图

模板编译简介

  • 模板编译的主要目的是将模板(template)转换成渲染函数(render)
    • 渲染函数
render (h) {
    return h('div', [
      h('h1', { on: { click: this.hander } }, 'title'),
      h('p', 'some content')
    ])
  }
  • 模板编译的作用
    • vue.2x使用VNode描述视图以及各种交互,用户自己编写VNode比较复杂
    • 用户只需要编写类似HTML的代码 - Vue.js模板,通过编译器将模板转换为返回VNode的render函数
    • .vue文件会被webpack在构建过程中转换成render函数(webpack本身是不支持将template转换成render函数的,内部是通过vue-loader来操作的,根据运行的时间,我们可以把编译的过程分为运行时编译和构建时编译)
      • 运行时编译的前提是必须使用完整版vue,因为完整版vue才带编译器,在项目运行的过程中将模板编译成render函数,这种方式的缺点是Vue体积大,运行速度慢
      • vue-cli创建的项目默认加载的是运行时版本的vue,它不带编译器,所以体积小,使用运行时版本的vue,因为它内部没有编译器,所以需要构建时编译,也就是打包的时候适应webpack加上vue-loader将模板编译成render函数,这种方式的好处是加载运行时版本的vue体积更小,运行时不需要额外的操作,所以运行速度快

模板编译的结果

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="app">
      <h1>Vue<span>模板编译过程</span>></h1>
      <p>{{msg}}</p>
      <comp @myclick="hander">
    </div>
    <!-- 完成版本 -->
    <script src="../../dist/vue.js"></script>
    <script>
      Vue.component('comp',{
        template:'<div> I am a comp </div>'
      })
      const vm = new Vue({
        el: "#app",
        data: {
          msg: "Hello compiler",
        },
        methods:{
          hander(){
            console.log('test')
          }
        }
      });
      // 编译生成的render函数
      console.log(this.$options.render)
    </script>
  </body>
</html>

  • 因为没有template,所以将el.outerHtml作为template
  • 打印的结果是
(function anonymous() {
        with (this) {
          return _c(
            "div",
            { attrs: { id: "app" } },
            [
              _m(0),
              _v(" "),
              _c("p", [_v(_s(msg))]),
              _v(" "),
              _c("comp", { on: { myclick: hander } }),
            ],
            1
          );
        }
      });
  • with(this)的作用是创建一个封闭的作用域,_m相当于this._m
    在这里插入图片描述
    在这里插入图片描述

  • -s的作用,将用户传入的内容转换成字符转,因为在html解析,自动对字符串进行解析,当行我们把msg的值改成[1,2,3],视图上显示的也是[1,2,3],实际上是将整个数组转换成了字符串
    在这里插入图片描述

  • 我们再来观察下这些下划线方法的定义位置
    在这里插入图片描述

Vue Template Explorer

  • 工具:帮助我们将html模板转换成render函数,可以通过该工具学习render函数
    在这里插入图片描述

编译的入口函数

  • render函数由compileToFunctions(template,options)函数生成,并将render函数绑定过到Vue实例的$options属性上(编译成函数)
    在这里插入图片描述
  • compileToFunctions函数又是createCompiler函数生成(创建编译器),这里只是创建了compileToFunctions还没使用到该函数
    在这里插入图片描述
    • baseOptions对象,最重要的是模块和指令

      • 模块:处理行内样式和类样式以及处理和v-if一起使用的v-model
      • 指令:v-指令名text/html/model
        在这里插入图片描述
      • 模块
        在这里插入图片描述
    • 指令
      在这里插入图片描述

    • createCompiler函数由createCompilerCreator函数生成

createCompilerCreator函数详解

  • 作用:闭包生成createCompiler函数,
  • 参数: baseCompile函数=>核心函数,该函数做了三件事
  1. 把模板template编译成抽象语法树ast
  2. 优化抽象语法树
  3. 把抽象语法树生成字符串形式的js代码
  • 之前都是函数调用时候传递的实参,我们在看下定义createCompilerCreator的地方

  • 在 src\compiler\index.js中介绍了createCompilerCreator实参baseCompile函数的功能,但要了解createCompilerCreator函数真正执行了什么功能,如何返回creteCompiler函数,还得去createCompilerCreator函数的定义的地方看
    在这里插入图片描述

  • createCompilerCreator函数定义的地方–src\compiler\create-compiler.js

  • createCompilerCreator函数的作用,经过参数baseCompile函数的处理,返回了createCompier函数,而createCompiler(baseOptions)函数最终又返回了compile函数和compileToFunctions函数组成的对象
    在这里插入图片描述

  • 总体的思维导图如下
    在这里插入图片描述

模板编译的过程

 - 在createCompiler函数中返回了入口函数compileToFunction,我们重点观察compileToFunction的定义

在这里插入图片描述

  • 观察下createCompileTofunctionFn(compile)函数做了什么(这个时候就可以看出前面定义compile函数的作用了),实际上createCompilerCreator函数的参数(baseCompile核心函数是在compile函数中调用的)
    在这里插入图片描述
    在这里插入图片描述

compile函数

  • 在createCompileTofunctionFn中实际上是调用了compile函数,我们重点观察compile函数做了什么事情
  • compile函数是在createCompiler函数中定义的
    在这里插入图片描述
  • compile函数的核心作用
    • 合并选项baseOptions+options=finalOptions
    • 调用baseCompile进行编译,返回编译好的对象

baseCompile-AST

  • 在compile函数中合并选项完成后通过baseCompile核心函数编译模板,生成包含抽象语法树的编译对象并且返回
  • 什么是抽象语法树(实质是一个对象)
  1. 抽象语法树简称AST(Abstract Ayntax Tree)
  2. 使用对象的形式描述树形的结构代码
  3. 此处的抽象语法树是用来描述树形结构的HTML字符串
  • 为什么要使用抽象语法树
  1. 模板字符串转换成AST后,可以通过AST对模板做优化处理
  2. 标记模板中的静态内容,在patch的时候直接跳过静态内容
  3. 在patch的过程中静态内容不需要对比和重新渲染

我们可以看下普通的html代码对应的抽象语法树是什么样的(https://astexplorer.net/)
在这里插入图片描述

baseCompile-parse

  • parse函数的作用试讲模板字符串转换成AST对象,这个过程比较复杂,vue内部借鉴了一个开源库来实现此功能,深入研究parse过程的事件和说活不成正比,所以这里我们只关注整体的执行流程

  • parse函数只接受两个参数:模板字符串-template及合并后的选项

  • 返回值是解析好的AST对象
    在这里插入图片描述

  • 我们进入parse函数内部看下,parse函数中解析了选项中的成员和定义了一些变量,其中最重要的是在pasre函数内部调用的parseHTML函数(用来解析template模板),返回的root对象就是我们解析好的AST对象
    在这里插入图片描述

  • 我们再观察parseHTML函数内部的处理过程,参数

    • 选项相关的参数
    • start/end等相关函数(解析到标签/注释/文本等内容的时候调用的方法,将相关处理的结果拼接到AST对象上)
      在这里插入图片描述
  • 以上的parseHTML函数的调用,我们再观察该函数的定义src\compiler\parser\html-parser.js

    • 首先定义了很多正则表达式,这些正则表达式的作用是用来匹配html字符串模板中的内容
      在这里插入图片描述
    • 再回到parseHTML函数中,这个函数会遍历html字符串(即传入的template),通过以上定义的正则表达式来判断html中的内容,是否是标签/属性/文本等,再调用parseHTML传入的start/end/coment等来处理正则匹配到的内容,然后进行拼接,我们以start方法来举例
      在这里插入图片描述
  • 首先start函数是正则匹配到开始标签的时候调用,内部首先会调用createASTElement创建AST对象

 // 创建AST对象
      let element: ASTElement = createASTElement(tag, attrs, currentParent)
  • 我们进入createASTElement函数来分析,这个函数非常的简单,就是返回了一个对象,这个对象就是我们说的AST对象,所以可以看出抽象语法树这个概念并不难,它就是一个对象.这个对象具备以下属性
    在这里插入图片描述

  • 通过createASTElement创建AST对象后,就是填充相关的属性了(包含指令)
    在这里插入图片描述

  • 以v-pre指令为例,我们进入processPre函数来看下v-pre属性的处理过程
    在这里插入图片描述

  • 进入getAndRemoveAttr函数看下
    在这里插入图片描述
    在这里插入图片描述

  • 处理完v-pre后,再处理v-for,v-if等结构化指令
    在这里插入图片描述

  • 我们以v-if举例 processIf(element)
    在这里插入图片描述

  • 不管是v-pre还是v-if等属性的处理过程,我们可以看出,都是将相关的属性值绑定到AST对象的相关属性上

  • 小结:parse这个函数内部处理过程中会依次去遍历html模板字符串,把html字符串转换成AST对象,也就是一个普通的对象,html中的属性和指令都会记录在AST对象的相应属性上

baseCompile-optimize

  • parse函数将html模板字符串转换成了AST对象,接下来解释optimize函数对AST对象的优化
  • optimize函数的作用:优化AST对象
    在这里插入图片描述
  • 我们进入optimize函数
    在这里插入图片描述
  • 我们首先观察markStatic函数,将AST对象相应的节点及子节点标记为是否是静态节点node.static=true/false
    在这里插入图片描述
  • 接下来我们观察markStaticRoots-静态根节点
    在这里插入图片描述
  • markStaticRoots小结:
  1. 静态根节点指的是,标签中包含子标签,并且没有动态内容,也就是子标签中都是纯文本内容
  2. 如果标签中包含纯文本内容,没有子标签,不是静态根节点,Vue中是不会对它做优化的,因为这样优化的成本大于收益

baseCompile-generate 上

  • generate函数的作用:将优化后的AST对象转换成js代码
    在这里插入图片描述

  • 我们进入generate函数,分析其实现
    在这里插入图片描述

  • 首先观察创建好的CodegenState对象(实例)
    在这里插入图片描述

  • 然后我们再观察genElement函数
    在这里插入图片描述

  • genElement函数返回的对象中render属性和staticRenderFns(静态根节点的渲染函数)属性都是字符串形式,还不是真正的渲染函数,我们再来看render真正转换成函数的位置-createCompileToFunctionFn函数中
    在这里插入图片描述

调试

  • 通过查看源码我们观察到模板编译是把模板字符串首先转换成AST对象,然后优化AST对象,优化的过程其实在标记静态根节点
  • 将优化好的AST对象转换成字符串形式的代码,最终再把字符串形式的代码通过new function转换成匿名函数,这个匿名函数就是最终的render函,模板编译最终就是把模板字符串转换成渲染函数

实例

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>compile</title>
  </head>
  <body>
    <div id="app">
      <!-- 静态根节点 -->
      <h1>Vue<span>模板编译过程</span>></h1>
      <!-- 非静态根节点,有插值表达式 -->
      <div>
        {{msg}}
        <p>hello</p>
      </div>
      <!-- 非静态根节点,因为没有元素子节点 -->
      <div>是否显示</div>
    </div>
    <!-- 完成版本 -->
    <script src="../../dist/vue.js"></script>
    <script>
      const vm = new Vue({
        el: "#app",
        data: {
          msg: "Hello compiler",
        },
        methods: {
          hander() {
            console.log("test");
          },
        },
      });
      // 编译生成的render函数
      console.log(vm.$options.render);
    </script>
  </body>
</html>

在这里插入图片描述
在这里插入图片描述

  • 因为首次编译,缓存中是没有编译好的渲染函数,所以继续执行compile函数,F11键入compile函数
    在这里插入图片描述
  • F11进入baseCompile函数
    在这里插入图片描述
  • F10直接跳过函数的运行看结果
    在这里插入图片描述
  • 注意:上面只是生成了AST对象,.并没有看到static等属性(优化后的AST对象才会有)
  • 执行完optimize函数后,优化后的AST对象有static等属性(首先是不是静态的,如果不是静态的,一定不是静态根节点)
    在这里插入图片描述
    在这里插入图片描述
  • 执行generate函数,生成js代码,AST对象中多了一个新属性staticProcessed,代表处理完毕
    在这里插入图片描述
    在这里插入图片描述
  • 再找到把render字符串转换成函数的位置
    在这里插入图片描述
  • 缓存rende函数
    在这里插入图片描述

模板编译过程思维导图

在这里插入图片描述


http://www.niftyadmin.cn/n/1499627.html

相关文章

自动化部署必备技能—定制化RPM包

回顾下安装软件的三种方式&#xff1a;1、编译安装软件&#xff0c;优点是可以定制化安装目录、按需开启功能等&#xff0c;缺点是需要查找并实验出适合的编译参数&#xff0c;诸如MySQL之类的软件编译耗时过长。 2、yum安装软件&#xff0c;优点是全自动化安装&#xff0c;不需…

1.0总结

GitHub develop分支 团队&#xff1a;盖嘉轩 031602211许郁杨 031602240 1.0主要是描述一下程序本身&#xff0c;也就是代码和编码规范部分&#xff0c;完整的总结会在完善一些细节后再提交。其实原定是应该在13号提交这篇文章的&#xff0c;只是我写的部分出了点问题。。 其实…

组件化

组件化组件化回顾组件注册组件注册方式全局组件局部组件调试组件注册的过程组件的创建过程回顾首次渲染过程组件化回顾 一个Vue组件就是一个拥有预定义选项的一个Vue实例一个组件可以组成页面上一个功能完备的区域,组件可以包含脚本,样式,模板 组件注册 组件注册方式 全局组…

JVM内存设置多大合适?Xmx和Xmn如何设置?

JVM内存设置多大合适&#xff1f;Xmx和Xmn如何设置&#xff1f; 问题:新上线一个java服务&#xff0c;或者是RPC或者是WEB站点&#xff0c; 内存的设置该怎么设置呢&#xff1f;设置成多大比较合适&#xff0c;既不浪费内存&#xff0c;又不影响性能呢&#xff1f; 分析&#x…

阿里云提示Discuz uc.key泄露导致代码注入漏洞uc.php的解决方法

适用所有用UC整合 阿里云提示漏洞&#xff1a; discuz中的/api/uc.php存在代码写入漏洞,导致黑客可写入恶意代码获取uckey,.......... 漏洞名称&#xff1a;Discuz uc.key泄露导致代码注入漏洞 补丁文件&#xff1a;/api/uc.php补丁来源&#xff1a;云盾自研 解决方法&#xff…

Intellij IDEA 14中使用MyBatis-generator 自动生成MyBatis代码

一&#xff1a;项目建立好及其基本的测试好 二&#xff1a;在maven项目的pom.xml 添加mybatis-generator-maven-plugin 插件 <build><finalName>CourseDesignManage</finalName><plugins><plugin><groupId>org.mybatis.generator</grou…

redis使用watch完成秒杀抢购功能

Redis使用watch完成秒杀抢购功能&#xff1a;使用redis中两个key完成秒杀抢购功能&#xff0c;mywatchkey用于存储抢购数量和mywatchlist用户存储抢购列表。它的优点如下&#xff1a;1. 首先选用内存数据库来抢购速度极快。2. 速度快并发自然没不是问题。3. 使用悲观锁&#xf…

CSS之BFC(Block Formatting Context)

这是我的第一篇博客&#xff0c;不知道从什么开始写起&#xff0c;那就从我现在看的开始写起吧。 以前我也不知道BFC是什么&#xff0c;今天看了才知道原来以前经常接触&#xff0c;只是不知道专业名称罢了。就像闭包、继承一样&#xff0c;以前经常用到&#xff0c;只是不知道…