Vue.js 框架源码与进阶 - Vue.js 源码剖析 - 模板编译

article/2025/5/3 2:22:37

文章目录

  • 一、模板编译简介
  • 二、体验模板编译的结果
  • 三、Vue Template Explorer
  • 四、编译的入口函数
  • 五、模板编译过程
    • 5.1 compileToFunctions
    • 5.2 compile
    • 5.3 baseCompile
      • 5.3.1 baseCompile-AST
      • 5.3.2 baseCompile-parse
      • 5.3.3 baseCompile-optimize
      • 5.3.4 baseCompile-generate
    • 5.4 模板编译过程总结

一、模板编译简介

  • 模板编译的主要目的是将模板 (template) 转换为渲染函数 (render)
<div><h1 @click="handler">title</h1><p>some content</p>
</div>
  • 渲染函数 render
render (h) {return h('div', [h('h1', { on: { click: this.handler} }, 'title'),h('p', 'some content')])
}
  • 模板编译的作用
    • Vue 2.x 使用 VNode 描述视图以及各种交互,用户自己编写 VNode 比较复杂
    • 用户只需要编写类似 HTML 的代码 - Vue.js 模板,通过编译器将模板转换为返回 VNode 的 render 函数
    • .vue 文件会被 webpack 在构建的过程中转换成 render 函数

二、体验模板编译的结果

  • 带编译器版本的 Vue.js 中,使用 template 或 el 的方式设置模板
<div id="app"><h1>Vue<span>模板编译过程</span></h1><p>{{ msg }}</p><comp @myclick="handler"></comp>
</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: {handler () {console.log('test')}}})console.log(vm.$options.render)
</script>
  • 编译后 render 输出的结果
(function anonymous() {// 匿名函数调用with 代码块使用this对象的成员可省略thiswith (this) {return _c("div", // tag标签,对应<div>{ attrs: { id: "app" } }, // data描述tag,对应id="app"[ // children设置tag子节点_m(0), // 处理静态内容做优化处理,对应<h1>_v(" "), // 创建空白的文本节点,对应<h1>和<p>之间的空白位置(换行)// 创建<p>对应的vnode 第二个位置(数组包裹的文本的vnode节点)_c("p", [_v(_s(msg))]), // 把用户输入数据转化为字符串(_s)_v(" "),_c("comp", { on: { myclick: handler } }), // 创建自定义组件对应的vnode],1 // 后续如何对children处理,将children拍平为一维数组);}
});
  • _c 是 createElement() 方法,定义的位置 instance/render.js 中
  • 相关的渲染函数(_开头的方法定义),在 instance/render-helps/index.js 中
// instance/render-helps/index.js
target._v = createTextVNode
target._s = toString
target._m = renderStatic// core/vdom/vnode.js
export function createTextVNode (val: string | number) {return new VNode(undefined, undefined, undefined, String(val))
}// shared/util
// 将一个值转换为实际渲染的字符串
export function toString (val: any): string {return val == null? '': Array.isArray(val) || (isPlainObject(val) && val.toString === _toString)? JSON.stringify(val, null, 2): String(val)
}// 在 instance/render-helps/render-static.js
// 用于渲染静态树的运行时帮助程序。
export function renderStatic (index: number,isInFor: boolean
): VNode | Array<VNode> {const cached = this._staticTrees || (this._staticTrees = [])let tree = cached[index]// if has already-rendered static tree and not inside v-for,// we can reuse the same tree.// 如果已经渲染了静态树,并且不在v-for里面,我们可以重用同样的树。if (tree && !isInFor) {return tree}// otherwise, render a fresh tree.// 如果没有,从staticRenderFns这个数组中获取静态根节点对应的render函数调用// 此时就生成vnode节点,把结果缓存tree = cached[index] = this.$options.staticRenderFns[index].call(this._renderProxy,null,this // for render fns generated for functional component templates)// 把当前返回的vnode节点标记为静态的// 将来调用patch函数的时候,内部会判断如果当前vnode为静态,则不再对比节点差异markStatic(tree, `__static__${index}`, false)return tree
}
  • 把 template 转换成 render 的入口 src\platforms\web\entry-runtime-with-compiler.js

三、Vue Template Explorer

把 html 模版转换成 render 函数的工具

  • vue-template-explorer
    • Vue 2.6 把模板编译成 render 函数的工具
    • 在使用vue2.x 的模板时,标签内的文本内容尽量不要添加多余的空白

模板

<div id="app"><select><option>{{ msg  }}</option></select><div>hello</div>
</div>

转换结果

function render() {with(this) {return _c('div', {attrs: {"id": "app"}}, [_c('select', [_c('option', [_v("\n      " + _s(msg) + "\n    ")])]),_c('div', [_v("\n    hello\n  ")])])}
}
  • vue-next-template-explorer
    • Vue 3.0 beta 把模板编译成 render 函数的工具
    • vue3 编译后的 render 函数已经去除了标签内多余的空白
import { toDisplayString as _toDisplayString, createElementVNode as _createElementVNode, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"export function render(_ctx, _cache, $props, $setup, $data, $options) {return (_openBlock(), _createElementBlock("div", { id: "app" }, [_createElementVNode("select", null, [_createElementVNode("option", null, _toDisplayString(_ctx.msg), 1 /* TEXT */)]),_createElementVNode("div", null, " hello ")]))
}// Check the console for the AST

四、编译的入口函数

  • src\platforms\web\entry-runtime-with-compiler.js
Vue.prototype.$mount = function (...// 把 template 转换成 render 函数const { render, staticRenderFns } = compileToFunctions(template, {outputSourceRange: process.env.NODE_ENV !== 'production',shouldDecodeNewlines,shouldDecodeNewlinesForHref,delimiters: options.delimiters,comments: options.comments}, this)options.render = renderoptions.staticRenderFns = staticRenderFns...
)

在这里插入图片描述

五、模板编译过程

5.1 compileToFunctions

  • src/compiler/to-function.js
export function createCompileToFunctionFn (compile: Function): Function {const cache = Object.create(null)return function compileToFunctions (template: string,options?: CompilerOptions,vm?: Component): CompiledFunctionResult {// 防止污染 vue 的 options 所以克隆一份options = extend({}, options)const warn = options.warn || baseWarndelete options.warn...// check cache// 1. 读取缓存中的 CompiledFunctionResult 对象,如果有直接返回const key = options.delimiters? String(options.delimiters) + template: templateif (cache[key]) {return cache[key]}// compile// 2. 把模板编译为编译对象(render, staticRenderFns),字符串形式的js代码const compiled = compile(template, options)...// 3. 把字符串形式的js代码转换成js方法res.render = createFunction(compiled.render, fnGenErrors)res.staticRenderFns = compiled.staticRenderFns.map(code => {return createFunction(code, fnGenErrors)})...// 4. 缓存并返回res对象(render, staticRenderFns方法)return (cache[key] = res)}
}

5.2 compile

  • src/compiler/create-compiler.js
export function createCompilerCreator (baseCompile: Function): Function {// baseOptions 平台相关的options// src\platforms\web\compiler\options.js 中定义return function createCompiler (baseOptions: CompilerOptions) {function compile (template: string,options?: CompilerOptions): CompiledResult {// 合并 baseOptions 和 complice函数传递过来的optionsconst finalOptions = Object.create(baseOptions)// 存贮编译过程中出现的错误和信息const errors = []const tips = []let warn = (msg, range, tip) => {(tip ? tips : errors).push(msg)}if (options) {...}finalOptions.warn = warn// 通过 baseCompile 把模板编译成 render函数const compiled = baseCompile(template.trim(), finalOptions)if (process.env.NODE_ENV !== 'production') {detectErrors(compiled.ast, warn)}compiled.errors = errorscompiled.tips = tipsreturn compiled}return {compile,compileToFunctions: createCompileToFunctionFn(compile)}}
}

5.3 baseCompile

  • src/compiler/index.js
// `createCompilerCreator` allows creating compilers that use alternative
// parser/optimizer/codegen, e.g the SSR optimizing compiler.
// Here we just export a default compiler using the default parts.
// `createCompilerCreator`允许创建使用替代解析器/优化器/代码生成的编译器,
// 例如SSR优化编译器。在这里,我们只是使用默认的部分导出一个默认的编译器。
export const createCompiler = createCompilerCreator(function baseCompile (template: string,options: CompilerOptions
): CompiledResult {// 把模板转换成 ast 抽象语法树// 抽象语法树,用来以树形的方式描述代码结构const ast = parse(template.trim(), options)if (options.optimize !== false) {// 优化抽象语法树optimize(ast, options)}// 把抽象语法树生成字符串形式的 js 代码const code = generate(ast, options)return {ast,// 渲染函数render: code.render,// 静态渲染函数,生成静态 VNode 树staticRenderFns: code.staticRenderFns}
})

5.3.1 baseCompile-AST

什么是抽象语法树

  • 抽象语法树简称 AST (Abstract Syntax Tree)
  • 使用对象的形式描述树形的代码结构
  • 此处的抽象语法树是用来描述树形结构的 HTML 字符串

为什么要使用抽象语法树

  • 模板字符串转换成 AST 后,可以通过 AST 对模板做优化处理
  • 标记模板中的静态内容,在 patch 的时候直接跳过静态内容
  • 在 patch 的过程中静态内容不需要对比和重新渲染

获取 AST

  • 使用工具 AST explorer

5.3.2 baseCompile-parse

  • 解析器将模板解析为抽象语树 AST,只有将模板解析成 AST 后,才能基于它做优化或者生成代码字符串
  • parse 函数内部处理过程中会依次去遍历 html 模板字符串,把其转换成 AST 对象,html 中的属性和指令(v-if、v-for 等)都会记录在 AST 对象的相应属性上
    • src/compiler/index.js
// 把模板转换成 AST 抽象语法树
// 抽象语法树,用来以树形的方式描述代码结构
const ast = parse(template.trim(), options)
  • v-if/v-for 结构化指令只能在编译阶段处理,如果我们要在 render 函数处理条件或循环只能使用 js 中的 if 和 for
 Vue.component("comp", {data: () => {return {msg: "my comp",};},render(h) {if (this.msg) {return h("div", this.msg);}return h("div", "bar");},});

5.3.3 baseCompile-optimize

  • src/compiler/index.js
if (options.optimize !== false) {// 优化抽象语法树optimize(ast, options)
}
  • src/compiler/optimizer.js
  • 优化抽象语法树,检测子节点中是否是纯静态节点(对应的 DOM 子树永远不会发生变化)
  • 一旦检测到纯静态节点
    • 提升为常量,重新渲染的时候不在重新创建节点
    • 在 patch 的时候直接跳过静态子树
/**- Goal of the optimizer: walk the generated template AST tree- and detect sub-trees that are purely static, i.e. parts of- the DOM that never needs to change.-  - Once we detect these sub-trees, we can:-  - 1. Hoist them into constants, so that we no longer need to-    create fresh nodes for them on each re-render;- 2. Completely skip them in the patching process.*/
// 优化的目的:标记抽象语法树的静态节点,即DOM中永远不需要改变的部分
// 当标记完静态子树后,将来就不需要进行渲染,在patch的时候直接跳过静态子树
// 一旦我们检测到这些子树,我们就可以做到: 
// 1. 将它们提升为常量,这样我们就不再需要在每次重新渲染时为它们创建新的节点;
// 2. 在修补过程中完全跳过它们。
export function optimize (root: ?ASTElement, options: CompilerOptions) {// 判断root,是否传递 AST 对象if (!root) returnisStaticKey = genStaticKeysCached(options.staticKeys || '')isPlatformReservedTag = options.isReservedTag || no// first pass: mark all non-static nodes.// 标记静态节点markStatic(root)// second pass: mark static roots.// 标记静态根节点markStaticRoots(root, false)
}

5.3.4 baseCompile-generate

  • src/compiler/index.js
// 把抽象语法树生成字符串形式的 js 代码
const code = generate(ast, options)
  • src/compiler/codegen/index.js
  • 把抽象语法树转换成字符串形式的 js 代码,生成 render 表达式
export function generate (ast: ASTElement | void,options: CompilerOptions
): CodegenResult {// 代码生成过程中使用到的状态对象const state = new CodegenState(options)// AST存在,调用genElement生成代码const code = ast ? genElement(ast, state) : '_c("div")'return {render: `with(this){return ${code}}`,staticRenderFns: state.staticRenderFns}
}
  • src\compiler\to-function.js
// 把字符串转换成函数
function createFunction (code, errors) {try {return new Function(code)} catch (err) {errors.push({ err, code })return noop}
}

5.4 模板编译过程总结

在这里插入图片描述


http://chatgpt.dhexx.cn/article/LA2SXJbn.shtml

相关文章

全栈低码开源框架 json-script-rule 配置说明

说明 配置主要分为映射信息配置和spring配置文件application的配置 映射信息 映射信息通常指的是所配置的po类与映射表的相关信息&#xff0c;通过相关注解来获取相关的静态信息&#xff0c;当然这里也可以通过其它的渠道来加载这些配置信息&#xff0c;如数据库、xml文件等…

全栈低码开源框架 json-script-rule 导入

说明 导入功能是将带有数据的excel文件上传后再通过程序导入到数据库&#xff0c;创建模型如下 Data public class JSRuleImport<A extends JSRuleAction<A>> implements IJSRuleActionModel<A>{/*** <p>导入excel对象&#xff0c;别名e* */JsonAlias…

Vue3 —— to 全家桶及源码学习

该文章是在学习 小满vue3 课程的随堂记录示例均采用 <script setup>&#xff0c;且包含 typescript 的基础用法 前言 本篇主要学习几个 api 及相关源码&#xff1a; toReftoRefstoRaw 一、toRef toRef(reactiveObj, key) 接收两个参数&#xff0c;第一个是 响应式对象…

vue框架安装mock

mock是&#xff1a;通过随机数据,模拟各种场景。 开发无侵入 不需要修改既有代码,就可以拦截 Ajax 请求,返回模拟的响应数据。 用法简单 符合直觉的接口。 1、安装mock前先安装axios请求 &#xff1a;** npm install axios --save** 安装成功axios的链接&#xff1a; 2、安装mo…

【甄选靶场】Vulnhub百个项目渗透——项目十六:FristiLeaks_1.3(文件上传,py脚本改写,sudo提权,脏牛提权,源码获取)

Vulnhub百个项目渗透 Vulnhub百个项目渗透——项目十六&#xff1a;FristiLeaks_1.3&#xff08;文件上传&#xff0c;py脚本改写&#xff0c;sudo提权&#xff0c;脏牛提权&#xff0c;源码获取&#xff09; 靶场地址 &#x1f525;系列专栏&#xff1a;Vulnhub百个项目渗透…

vue全家桶——vuex

本文主要介绍vuex【状态管理模式】&#xff0c;在介绍vuex前&#xff0c;需要重点说下一些概念 vue最大的特点之一。数据驱动视图&#xff0c;可以吧数据理解成状态&#xff0c;视图-view可以理解成用户看到的页面&#xff0c;页面是动态变化的&#xff0c;而这个变化必须是有…

博客园滑块验证码破解

极验最初的滑块验证码是两张图&#xff0c;首先出现的是原图&#xff0c;点一下出现凹槽&#xff0c;然后拖动滑块进去&#xff0c;注意拖拽速度就可以破解成功。 原理&#xff1a; 分别遍历扫描原图和有凹槽的图片像素&#xff0c;进行对比&#xff0c;像素不一致的位置就是凹…

尚硅谷Vue2.0+Vue3.0全套教程视频笔记 + 代码 [P001-050]

视频链接&#xff1a;尚硅谷Vue2.0Vue3.0全套教程丨vuejs从入门到精通_哔哩哔哩_bilibili P1-50&#xff1a;当前页面。 P51-100&#xff1a;尚硅谷Vue2.0Vue3.0全套教程视频笔记 代码 [P051-100]_小白桶子的博客-CSDN博客 P101-135&#xff1a;尚硅谷Vue2.0Vue3.0全套教程视…

【Vue】Mock.js的使用教程,入门级(附代码和案例)

Mock.js的使用&#xff08;附代码和案例&#xff09; 1、什么是mockjs 生成随机数据&#xff0c;拦截Ajax请求 了解一项技术&#xff0c;官网当然要知道 Mock.js 官网 2、程序 前端&#xff1a;访问后端接口&#xff0c;展示数据后端&#xff1a;后端负责业务逻辑&#xff0c…

YDOOK:vue3.0: vue-cli4.5: 引入 Element PLUS 的正确方法 Vue-cli4.5 引入 element+ plus

YDOOK&#xff1a;vue3.0: vue-cli4.5: 引入 Element PLUS 的正确方法 Vue-cli4.5 引入 element plus 1. 官网的指导安装使用方式尚未更新&#xff0c;显示的是&#xff1a; 2. 如果使用的是 npm 安装, 输入的代码为&#xff1a; npm install element-plus --save大陆建议使用…

使用Xposed对软件进行破解

入门 去AS里面新建一个NoActivity项目&#xff0c;最好把minimum版本调小 <!-- 加载Xposed模块--><meta-dataandroid:name"xposedmodule"android:value"true" /> <!-- 模块描述--><meta-dataandroid:name"xpose…

破解root密码精简版

破解root密码精简版&#xff1a; 1、重启虚拟机 2、在linux16末尾加上 rd.break,ctrlx执行 3、mount -o remount,rw /sysroot 把根以读写的方式挂载 挂在系统的根sysroot 4、chroot /sysroot 5、passwd root 6、输入新密码&#xff1a; 7、确认密码 8、touch /.autorelabel 标…

【word论文排版教程4】样式的应用

【word论文排版教程4】样式的应用 在使用样式处选择相应样式&#xff0c;同样可以使用之前设置的快捷键 标题比较长&#xff0c;使用软回车进行换行

【word论文排版教程2】论文章节安排及分节

【word论文排版教程2】论文章节安排及分节 论文章节可分为如下8个章节&#xff1a; 封面 中文摘要 英文摘要 目录 正文 参考文献 附录 致谢 输入封面&#xff0c;添加分解符 添加分解符效果 同样方法为其它章节添加分解符。

【word论文排版教程1】页面设置

【word论文排版教程1】页面设置 页面设置要求如下&#xff1a; 页面格式&#xff1a;纸张A4&#xff0c;上2.6cm&#xff0c;下2.6cm&#xff0c;左2.5cm&#xff0c;右2cm&#xff0c;装订线位置左侧&#xff0c;装订线0cm&#xff0c;无文档网格 页面布局->页面设置

毕业必备!推荐收藏的学位论文排版教程(word完整版)

本文将介绍学位论文的页面布局&#xff0c;标题格式&#xff0c;文档生成列表&#xff0c;插入公式&#xff0c;页眉页脚&#xff0c;生成目录、表格和图片的交叉引用、插入参考文献、英语翻译校对等内容和技巧&#xff0c;学习内容偏多&#xff0c;同学们可以先收藏下来&#…

毕业论文word文档排版教程(动图的方式演示,针对wps)

很久之前写好的文章&#xff0c;不知不觉毕业了那么久了 目录 1 常用快捷键 2 显示全部格式标记 3 因有手动换行符无法实现首行缩进 4 利用表格制作矩阵 5 因有英文(代码)和中文而导致空隙过大 6 设置页码和目录 xx 参考文献自动编号并更新文章中的序号 xxx 页…

Latex基本使用:论文排版

文章目录 前言一、参考文献引用二、插入符号1.插入希腊字母2.插入符号 三、插入公式三、插入图片总结 前言 记录使用Latex排版论文的方法。 一、参考文献引用 引用参考文献使用的是bib文件&#xff0c;首先在tex文件所在目录下新建txt文件&#xff0c;修改后缀为bib。 在谷歌…

texlive2020 + vscode 论文排版教程

这是我准备公开的第二篇技术博客&#xff0c;之前也写过一些简单的&#xff08;拿不出手的&#xff09;博文&#xff0c;但一直没信心公开&#xff0c;不过最后觉得也没啥&#xff0c;大家可以交流嘛&#xff0c;万一你写的有错&#xff0c;有好心人看见了指出的话那也是极好的…

毕业论文排版教程(word)桂林电子科技大学

摘要&#xff1a;满心欢喜地给导数发去初稿&#xff0c;换来了一句“格式乱起八糟”&#xff0c;你是否还在为论文排版而苦不堪言&#xff0c;认真看完这份教程&#xff0c;让你轻松搞定毕业论文排版。 毕业论文排版教程&#xff08;word&#xff09;桂林电子科技大学 样式图片…