01- vdom 和模板编译源码

组件渲染的过程

template --> ast --> render --> vDom --> 真实的Dom --> 页面

Runtime-Compiler和Runtime-Only的区别 - 简书

编译步骤

模板编译是Vue中比较核心的一部分。关于 Vue 编译原理这块的整体逻辑主要分三个部分,也可以说是分三步,前后关系如下:

第一步:将模板字符串转换成element ASTs( 解析器 parse

第二步:对 AST 进行静态节点标记,主要用来做虚拟DOM的渲染优化(优化器 optimize

第三步:使用element ASTs生成render函数代码字符串(代码生成器 generate

 编译后的AST结构

template 模板:

<div class="box"><p>{{name}}</p>
</div>

AST 抽象语法树:

ast: {tag: "div" //  元素标签名type: 1,  // 元素节点类型 1标签 2包含字面量表达式的文本节点 3普通文本节点或注释节点staticRoot: false, // 是否静态根节点static: false,  // 是否静态节点plain: true, parent: undefined,attrsList: [], // 标签节点的属性名和值的对象集合attrsMap: {}, // 和attrsList类似,不同在它是以键值对保存属性名和值children: [{tag: "p"type: 1,staticRoot: false,static: false,plain: true,parent: {tag: "div", ...},attrsList: [],attrsMap: {},children: [{type: 2,text: "{{name}}",static: false,expression: "_s(name)"  // type为2时才有这个属性,表示表达式的内容}]}]
}

generate,将AST转换成可以直接执行的JavaScript字符串

with(this) {return _c('div', [_c('p', [_v(_s(name))]), _v(" "), _m(0)])
}

注意一:平常开发中 我们使用的是不带编译版本的 Vue 版本(runtime-only)直接在 options 传入 template 选项 在开发环境报错

注意二:这里传入的 template 选项不要和.vue 文件里面的模板搞混淆了 vue 单文件组件的 template 是需要 vue-loader 进行处理的

我们传入的 el 或者 template 选项最后都会被解析成 render 函数 这样才能保持模板解析的一致性

以下代码实现是在在entry-runtime-with-compiler.js里面,和runtime-only版本需要区分开

1、模板编译入口

export function initMixin (Vue) {Vue.prototype._init = function (options) {const vm = this;vm.$options = options;initState(vm);// 如果有 el 属性,进行模板渲染if (vm.$options.el) {vm.$mount(vm.$options.el)}}Vue.prototype.$mount = function (el) {const vm = this;const options = vm.$options;el = document.querySelector(el);// 不存在 render 属性的几种情况if (!options.render) {// 不存在 render 但存在 templatelet template = options.template;// 不存在 render 和 template 但是存在 el 属性,直接将模板赋值到 el 所在的外层 html 结构 (就是 el 本身,并不是父元素)if (!template && el) {template = el.outerHTML;}// 最后把处理好的 template 模板转化成 render 函数if (template) {const render = compileToFunctions(template);options.render = render;}}}
}
  • 先初始化状态,initState(vm)
  • 再在 initMixin 函数中,判断是否有el,有则直接调用 vm.$mount(vm.$options.el) 进行模板渲染,没有则手动调用;
  • Vue.prototype.$mount 方法中,判断是否存在 render 属性,存在则给 template 赋值,如果不存在 render 和 template 但存在 el 属性,直接将模板赋值到 el 所在的外层 html 结构(就是el本身,并不是父元素);
  • 通过 compileToFunctions 将处理好的 template 模板转换成 render 函数

咱们主要关心$mount 方法 最终将处理好的 template 模板转成 render 函数

2、模板转化核心方法 compileToFunctions

export function compileToFunctions (template) {// html → ast → render函数// 第一步 将 html 字符串转换成 ast 语法树let ast = parse(template);// 第二步 优化静态节点if (mergeOptions.optimize !== false) {optimize(ast, options);}// 第三步 通过 ast 重新生成代码let code = generate(ast);// 使用 with 语法改变作用域为 this, 之后调用 render 函数可以使用 call 改变 this,方便 code 里面的变量取值。let renderFn = new Function(`with(this){return ${code}}`);return renderFn;
}

html → ast → render函数

这里需要把 html 字符串变成 render 函数,分三步:

(1)parse函数 把 HTML 代码转成 AST 语法树

        AST 是用来描述代码本身形成的树形结构,不仅可以描述 HTML,也可以描述 css 和 js 语法;很多库都运用到了 AST,比如 webpack,babel,eslint 等

(2) optimize函数 优化静态节点(主要用来做虚拟DOM的渲染优化)

        先遍历 AST,对 AST 进行静态节点标记,(即节点永远不会发生变化)并做出一些特殊的处理。例如:

  • 移除静态节点的 v-once 指令,因为它在这里没有任何意义。
  • 移除静态节点的 key 属性。因为静态节点永远不会改变,所以不需要 key 属性。
  • 将一些静态节点合并成一个节点,以减少渲染的节点数量。例如相邻的文本节点和元素节点可以被合并为一个元素节点。

(3)generate函数 通过 AST 重新生成代码

最后生成的代码需要和 render 函数一样

类似这样的结构:

_c('div',{id:"app"},_c('div',undefined,_v("hello"+_s(name)),_c('span',undefined,_v("world"))))
  • _c 代表 createElement 创建节点
  • _v 代表 createTextVNode 创建文本节点
  • _s 代表 toString 把对象解析成字符串
     

模板引擎的实现原理  with + new Function,使用 with 语法改变作用域为 this, 之后调用 render 函数可以使用 call 改变 this,方便 code 里面的变量取值。

parse函数,解析 html 并生成 ast

// src/compiler/parse.js// 以下为源码的正则  对正则表达式不清楚的同学可以参考小编之前写的文章(前端进阶高薪必看 - 正则篇);
const ncname = `[a-zA-Z_][\\-\\.0-9_a-zA-Z]*`; //匹配标签名 形如 abc-123
const qnameCapture = `((?:${ncname}\\:)?${ncname})`; //匹配特殊标签 形如 abc:234 前面的abc:可有可无
const startTagOpen = new RegExp(`^<${qnameCapture}`); // 匹配标签开始 形如 <abc-123 捕获里面的标签名
const startTagClose = /^\s*(\/?)>/; // 匹配标签结束  >
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`); // 匹配标签结尾 如 </abc-123> 捕获里面的标签名
const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/; // 匹配属性  形如 id="app"let root, currentParent; //代表根节点 和当前父节点
// 栈结构 来表示开始和结束标签
let stack = [];
// 标识元素和文本type
const ELEMENT_TYPE = 1;
const TEXT_TYPE = 3;
// 生成ast方法
function createASTElement(tagName, attrs) {return {tag: tagName,type: ELEMENT_TYPE,children: [],attrs,parent: null,};
}// 对开始标签进行处理
function handleStartTag({ tagName, attrs }) {let element = createASTElement(tagName, attrs);if (!root) {root = element;}currentParent = element;stack.push(element);
}// 对结束标签进行处理
function handleEndTag(tagName) {// 栈结构 []// 比如 <div><span></span></div> 当遇到第一个结束标签</span>时 会匹配到栈顶<span>元素对应的ast 并取出来let element = stack.pop();// 当前父元素就是栈顶的上一个元素 在这里就类似divcurrentParent = stack[stack.length - 1];// 建立parent和children关系if (currentParent) {element.parent = currentParent;currentParent.children.push(element);}
}// 对文本进行处理
function handleChars(text) {// 去掉空格text = text.replace(/\s/g, "");if (text) {currentParent.children.push({type: TEXT_TYPE,text,});}
}// 解析标签生成ast核心
export function parse(html) {while (html) {// 查找<let textEnd = html.indexOf("<");// 如果<在第一个 那么证明接下来就是一个标签 不管是开始还是结束标签if (textEnd === 0) {// 如果开始标签解析有结果const startTagMatch = parseStartTag();if (startTagMatch) {// 把解析好的标签名和属性解析生成asthandleStartTag(startTagMatch);continue;}// 匹配结束标签</const endTagMatch = html.match(endTag);if (endTagMatch) {advance(endTagMatch[0].length);handleEndTag(endTagMatch[1]);continue;}}let text;// 形如 hello<div></div>if (textEnd >= 0) {// 获取文本text = html.substring(0, textEnd);}if (text) {advance(text.length);handleChars(text);}}// 匹配开始标签function parseStartTag() {const start = html.match(startTagOpen);if (start) {const match = {tagName: start[1],attrs: [],};//匹配到了开始标签 就截取掉advance(start[0].length);// 开始匹配属性// end代表结束符号>  如果不是匹配到了结束标签// attr 表示匹配的属性let end, attr;while (!(end = html.match(startTagClose)) &&(attr = html.match(attribute))) {advance(attr[0].length);attr = {name: attr[1],value: attr[3] || attr[4] || attr[5], //这里是因为正则捕获支持双引号 单引号 和无引号的属性值};match.attrs.push(attr);}if (end) {//   代表一个标签匹配到结束的>了 代表开始标签解析完毕advance(1);return match;}}}//截取html字符串 每次匹配到了就往前继续匹配function advance(n) {html = html.substring(n);}//   返回生成的astreturn root;
}

generate函数,把 ast 转化成 render 函数结构

// src/compiler/codegen.jsconst defaultTagRE = /\{\{((?:.|\r?\n)+?)\}\}/g; //匹配花括号 {{  }} 捕获花括号里面的内容function gen(node) {// 判断节点类型// 主要包含处理文本核心// 源码这块包含了复杂的处理  比如 v-once v-for v-if 自定义指令 slot等等  咱们这里只考虑普通文本和变量表达式{{}}的处理// 如果是元素类型if (node.type == 1) {//   递归创建return generate(node);} else {//   如果是文本节点let text = node.text;// 不存在花括号变量表达式if (!defaultTagRE.test(text)) {return `_v(${JSON.stringify(text)})`;}// 正则是全局模式 每次需要重置正则的lastIndex属性  不然会引发匹配buglet lastIndex = (defaultTagRE.lastIndex = 0);let tokens = [];let match, index;while ((match = defaultTagRE.exec(text))) {// index代表匹配到的位置index = match.index;if (index > lastIndex) {//   匹配到的{{位置  在tokens里面放入普通文本tokens.push(JSON.stringify(text.slice(lastIndex, index)));}//   放入捕获到的变量内容tokens.push(`_s(${match[1].trim()})`);//   匹配指针后移lastIndex = index + match[0].length;}// 如果匹配完了花括号  text里面还有剩余的普通文本 那么继续pushif (lastIndex < text.length) {tokens.push(JSON.stringify(text.slice(lastIndex)));}// _v表示创建文本return `_v(${tokens.join("+")})`;}
}// 处理attrs属性
function genProps(attrs) {let str = "";for (let i = 0; i < attrs.length; i++) {let attr = attrs[i];// 对attrs属性里面的style做特殊处理if (attr.name === "style") {let obj = {};attr.value.split(";").forEach((item) => {let [key, value] = item.split(":");obj[key] = value;});attr.value = obj;}str += `${attr.name}:${JSON.stringify(attr.value)},`;}return `{${str.slice(0, -1)}}`;
}// 生成子节点 调用gen函数进行递归创建
function getChildren(el) {const children = el.children;if (children) {return `${children.map((c) => gen(c)).join(",")}`;}
}
// 递归创建生成code
export function generate(el) {let children = getChildren(el);let code = `_c('${el.tag}',${el.attrs.length ? `${genProps(el.attrs)}` : "undefined"}${children ? `,${children}` : ""})`;return code;
}

code 字符串生成 render 函数

export function compileToFunctions (template) {let ast = parseHTML(template);let code = genCode(ast);// 将模板变成 render 函数,通过 with + new Function 的方式让字符串变成 JS 语法来执行const render = new Function(`with(this){return ${code}}`);return render;
}

 https://juejin.cn/spost/7267858684667035704

Vue 模板 AST 详解 - 文章教程 - 文江博客

滑动验证页面

手写Vue2.0源码(二)-模板编译原理|技术点评_慕课手记

前端鲨鱼哥 的个人主页 - 文章 - 掘金

前端进阶高薪必看-正则篇 - 掘金

从vue模板解析学习正则表达式

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

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

相关文章

周期 角频率 频率 振幅 初相角

周期 角频率 频率 振幅 初相角 当我们谈论傅里叶级数或波形分析时&#xff0c;以下术语经常出现&#xff1a; 周期 T T T: 函数在其图形上重复的时间或空间的长度。周期的倒数是频率。 频率 f f f: 周期的倒数&#xff0c;即一秒内波形重复的次数。单位通常为赫兹&#xff…

20、stm32使用FMC驱动SDRAM(IS42S32800G-6BLI)

本文将使用安富莱的STM32H743XIH板子驱动SDRAM 引脚连接情况 一、CubeMx配置工程 1、开启调试口 2、开启外部高速时钟 配置时钟树 3、开启串口1 4、配置MPU 按照安富莱的例程配置&#xff1a; /* ********************************************************************…

双链表的插入,删除以及遍历

在上一节我们讲解了单链表的头插法和尾插法 http://t.csdn.cn/RixAu 但是单链表无法反向检索&#xff0c;对于某些情景可能造成不便&#xff0c;所以我们今天学习双链表 目录 1.双链表的初始化 2.双链表的插入 3.双链表的删除 4.遍历双链表 1.双链表的初始化 typedef i…

(二)结构型模式:8、代理模式(Proxy Pattern)(C++示例)

目录 1、代理模式&#xff08;Proxy Pattern&#xff09;含义 2、代理模式的UML图学习 3、代理模式的应用场景 4、代理模式的优缺点 5、C实现代理模式的实例 1、代理模式&#xff08;Proxy Pattern&#xff09;含义 代理模式&#xff08;Proxy&#xff09;&#xff0c;为…

离谱的Bug

离谱的 Bug Bug 情况发现 Bug修改 Bug其他感受历史 Bug火星Spirit号Mars Global Surveyor任务 Bug 情况 有一次&#xff0c;我在开发一个网页应用程序时&#xff0c;遇到了一个令人目瞪口呆的Bug。这个Bug出现在一个特定的页面上&#xff0c;当用户点击某个按钮时&#xff0c;…

智安网络|深入比较:Sass系统与源码系统的差异及选择指南

随着前端开发的快速发展&#xff0c;开发人员需要使用更高效和灵活的工具来处理样式表。在这个领域&#xff0c;Sass系统和源码系统是两个备受关注的选项。 Sass系统 Sass&#xff08;Syntactically Awesome Style Sheets&#xff09;是一种CSS预处理器&#xff0c;它扩展了CS…

在 OpenCV 中使用深度学习进行年龄检测-附源码

文末附完整源码和模型文件下载链接 在本教程中,我们将了解使用 OpenCV 创建年龄预测器和性别分类器项目的整个过程。 年龄检测 我们的目标是创建一个程序,使用图像来预测人的性别和年龄。但预测年龄可能并不像你想象的那么简单,为什么呢?您可能会认为年龄预测是一个回归问…

【Linux命令详解 | wget命令】 wget命令用于从网络下载文件,支持HTTP、HTTPS和FTP协议

文章标题 简介一&#xff0c;参数列表二&#xff0c;使用介绍1. 基本文件下载2. 递归下载整个网站3. 限制下载速率4. 防止SSL证书校验5. 断点续传6. 指定保存目录7. 自定义保存文件名8. 增量下载9. 使用HTTP代理10. 后台下载 总结 简介 在编程世界中&#xff0c;处理网络资源是…

快速搭建图书商城小程序的简易流程与优势

很多人喜欢阅读电子书&#xff0c;又有很多人依旧喜欢实体书&#xff0c;而实体书店拥有一个图书商城小程序便成为了满足用户需求的理想选择。如果您也想进入这一充满潜力的领域&#xff0c;但担心开发难度和复杂流程&#xff0c;别担心&#xff01;您能做到快速搭建一个专业、…

CXL 寄存器介绍 (1) - 寄存器分类

&#x1f525;点击查看精选 CXL 系列文章&#x1f525; &#x1f525;点击进入【芯片设计验证】社区&#xff0c;查看更多精彩内容&#x1f525; &#x1f4e2; 声明&#xff1a; &#x1f96d; 作者主页&#xff1a;【MangoPapa的CSDN主页】。⚠️ 本文首发于CSDN&#xff0c…

Mac安装opencv后无法导入cv2的解决方法

前提条件&#xff1a;以下两个插件安装成功 pip install opencv-python pip install --user opencv-contrib-python 注&#xff1a;直接用pip install opencv-contrib-python如果报错&#xff0c;就加上“–user" 第一步&#xff1a; 设置–添加python解释器 第二步&am…

机器学习|Softmax 回归的数学理解及代码解析

机器学习&#xff5c;Softmax 回归的数学理解及代码解析 Softmax 回归是一种常用的多类别分类算法&#xff0c;适用于将输入向量映射到多个类别的概率分布。在本文中&#xff0c;我们将深入探讨 Softmax 回归的数学原理&#xff0c;并提供 Python 示例代码帮助读者更好地理解和…

Python入门--关键字

关键字是Python编程语言中具有特殊含义的保留单词&#xff0c;不能用作变量名、函数名、类名或其他标识符。以下是Python 3.9.0版本中的关键字列表&#xff1a; False, None, True, and, as, assert, async, await, break, class, continue, def, del, elif, else, except, f…

Microsoft 图像BERT,基于大规模图文数据的跨模态预训练

视觉语言任务是当今自然语言处理&#xff08;NLP&#xff09;和计算机视觉领域的热门话题。大多数现有方法都基于预训练模型&#xff0c;这些模型使用后期融合方法融合下游任务的多模态输入。然而&#xff0c;这种方法通常需要在训练期间进行特定的数据注释&#xff0c;并且对于…

web前端开发基础入门html5+css3+js学习笔记(一)

目录 1.第一个前端程序2.前端工具的选择与安装3.VSCode开发者工具快捷键4.HTML5简介与基础骨架4.1 HTML5的DOCTYPE声明4.2 HTML5基本骨架4.2.1 html标签4.2.2 head标签4.2.3 body标签4.2.4 title标签4.2.5 meta标签 5.标签之标题5.1 快捷键5.1 标题标签位置摆放 6.标签之段落、…

Docker实战专栏简介

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…

星星之火:国产讯飞星火大模型的实际使用体验(与GPT对比)

#AIGC技术内容创作征文&#xff5c;全网寻找AI创作者&#xff0c;快来释放你的创作潜能吧&#xff01;# 文章目录 1 前言2 测试详情2.1 文案写作2.2 知识写作2.3 阅读理解2.4 语意测试&#xff08;重点关注&#xff09;2.5 常识性测试&#xff08;重点关注&#xff09;2.6 代码…

深入学习SpringCloud Alibaba微服务架构,揭秘Nacos、Sentinel、Seata等核心技术,助力构建高效系统!

课程链接&#xff1a; 链接: https://pan.baidu.com/s/1hRN0R8VFcwjyCTWCEsz-8Q?pwdj6ej 提取码: j6ej 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 --来自百度网盘超级会员v4的分享 课程介绍&#xff1a; &#x1f4da;【第01阶段】课程简介&#xff1a;全…

flinksql实时统计程序背压延迟优化

问题&#xff1a; flinkcdcflinksql做实时读取sls日志和实时统计业务指标&#xff0c;今天发现程序背压了&#xff0c;业务延迟了6个小时。解决办法&#xff1a; 1、资源优化 作业并发大时&#xff1a;在作业的高级配置的资源配置中&#xff0c;增加JobManager的资源&#xf…

2023国赛数学建模思路 - 复盘:校园消费行为分析

文章目录 0 赛题思路1 赛题背景2 分析目标3 数据说明4 数据预处理5 数据分析5.1 食堂就餐行为分析5.2 学生消费行为分析 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 赛题背景 校园一卡通是集…