大白话Vue源码系列(03):生成render函数
本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单。每一种语法指令都要考虑到,处理起来相当复杂。上篇已经生成了 AST,本篇依然对 Vue 源码做简化处理,探究 Vue 是如果根据 AST 生成所需要的 render 函数的。
优化 AST
优化 AST 的目的是优化整体性能,避免不必要计算。比如那些不存在数据绑定的节点,即纯静态的(purely static)在更新视图时根本不需要改变,因此在数据批处理,页面重渲染时可直接跳过它们。
Vue 通过遍历 AST 找出内容为纯静态的节点并将其标记为 static:
function optimize (root) {
//-- 第一步 标记 AST 所有静态节点 --
markStatic(root)
//-- 第二步 标记 AST 所有父节点(即子树根节点) --
markStaticRoots(root, false)
}
首先标记所有静态节点:
function markStatic (node) {
// 标记
if (node.type === 2) { // 插值表达式
node.static = false
}
if (node.type === 3) { // 普通文本
node.static = true
}
if (node.type === 1) { // 元素
// 如果所有子节点均是 static,则该节点也是 static
for (let i = 0; i < node.children.length; i++) {
const child = node.children[i]
markStatic(child)
if (!child.static) {
node.static = false
}
}
}
}
ASTNode 的 type 字段用于标识节点的类型,可查看上一篇的 AST 节点定义:type 为 1 表示元素,type 为 2 表示插值表达式,type 为 3 表示普通文本。可以看到,在标记 ASTElement 时会依次检查所有子元素节点的静态标记,从而得出该元素是否为 static。
上面 markStatic 函数使用的是树形数据结构的深度优先遍历算法,使用递归实现。
接下来标记出静态子树:
function markStaticRoots (node) {
if (node.type === 1) {
// For a node to qualify as a static root, it should have children that
// are not just static text. Otherwise the cost of hoisting out will
// outweigh the benefits and it's better off to just always render it fresh.
if (node.static && node.children.length && !(
node.children.length === 1 &&
node.children[0].type === 3
)) {
node.staticRoot = true
return
} else {
node.staticRoot = false
}
for (let i = 0; i < node.children.length; i++) {
markStaticRoots(node.children[i])
}
}
}
markStaticRoots 函数里并没有什么特别的地方,仅仅是对静态节点又做了一层筛选。请注意函数中的那几行注释:
For a node to qualify as a static root, it should have children that are not just static text. Otherwise the cost of hoisting out will outweigh the benefits and it's better off to just always render it fresh.
翻译过来大概意思是:一个节点如果想要成为静态根,它的子节点不能单纯只是静态文本。否则,把它单独提取出来还不如重渲染时总是更新它性能高。
这也是为什么要在标记了所有 AST 节点之后又要标记一遍静态子树根。
生成 render 函数
不了解 render 函数的可以先看一下 Vue 的 render 函数。不了解也没关系,就把它当成 Vue 最终需要的一段特定字符串拼接就行了。
function generate (el) {
if (el.staticRoot && !el.staticProcessed) {
return genStatic(el)
} else if (el.once && !el.onceProcessed) {
return genOnce(el)
} else if (el.for && !el.forProcessed) {
return genFor(el)
} else if (el.if && !el.ifProcessed) {
return genIf(el)
} else if (el.tag === 'template' && !el.slotTarget) {
return genChildren(el) || 'void 0'
} else if (el.tag === 'slot') {
return genSlot(el)
} else {
// component or element
let code
if (el.component) {
code = genComponent(el.component, el)
} else {
const data = el.plain ? undefined : genData(el)
const children = el.inlineTemplate ? null : genChildren(el)
code = `_c('${el.tag}'${
data ? `,${data}` : '' // data
}${
children ? `,${children}` : '' // children
})`
}
return code
}
}
上面生成 render 函数的过程比较繁琐,需要对不同情况作单独处理,这里不再一一展开。感兴趣的可在 Vue 项目的 src/compiler/codegen/index.js 文件里仔细研究一下。
现在结合生成 AST 的方法就可以将编译器初探中遗留的 compileToFunctions 方法定义出来了:
function compileToFunctions(el){
let ast = parseHTML(el)
optimize(ast)
return generate(ast)
}
也就是之前说的三步走:
- 将 html 模板解析成抽象语法树(AST)。
- 对 AST 做优化处理。
- 根据 AST 生成 render 函数。
小结
Vue 首先根据使用者的传参获得待编译的模板片段,然后使用正则匹配对片段里的标签各个击破,用步步蚕食的方法将整块模板片段最终解析成一棵 AST。获得 AST 后,后续的处理就非常方便了。Vue 会首先优化这棵 AST,将其中的静态子树找出来,这些静态节点在之后的视图更新和数据计算中是可以忽略掉的,从而提高性能。最后 Vue 遍历这棵 AST 的每个节点,根据节点的类型生成不同的函数碎片,最后拼接成整个 render 函数。
值得一提的是,将 AST 作为编译的中间形式是非常方便的,当 AST 构建出来之后,使用树形结构的深度优先遍历算法就可以方便地对树的每一个节点做处理。Vue 最后生成 render 函数也是通过遍历 AST ,根据每个节点生成函数的一小部分,最后拼接成整个函数。
本篇完,Vue 编译器部分到此完结。将在下篇开始进行 Vue 运行时相关的代码剖析,运行时这部分代码应该会是非常有意思的,包括 Vue 对象实例化,双向绑定,虚拟 DOM 等内容。
本系列会以每周一篇的速度持续更新,喜欢的小伙伴记得点关注哦。
大白话Vue源码系列(03):生成render函数的更多相关文章
- 大白话Vue源码系列(03):生成AST
阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 AST ...
- 大白话Vue源码系列(04):生成render函数
阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...
- 大白话Vue源码系列(02):编译器初探
阅读目录 编译器代码藏在哪 Vue.prototype.$mount 构建 AST 的一般过程 Vue 构建的 AST 题接上文,上回书说到,Vue 的编译器模块相对独立且简单,那咱们就从这块入手,先 ...
- 大白话Vue源码系列(05):运行时鸟瞰图
阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...
- 大白话Vue源码系列(01):万事开头难
阅读目录 Vue 的源码目录结构 预备知识 先捡软的捏 Angular 是 Google 亲儿子,React 是 Facebook 小正太,那咱为啥偏偏选择了 Vue 下手,一句话,Vue 是咱见过的 ...
- 大白话Vue源码系列目录
.first-level{ font-size: 1.2rem; cursor: default; color: #666; } .second-level{ font-size: 1.1rem; p ...
- 手牵手,从零学习Vue源码 系列一(前言-目录篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 手牵手,从零学习Vue源码 系列三(虚拟DOM篇) 陆续更新中... 预计八月中旬更新 ...
- 手牵手,从零学习Vue源码 系列二(变化侦测篇)
系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...
- 【Vue】VUE源码中的一些工具函数
Vue源码-工具方法 /* */ //Object.freeze()阻止修改现有属性的特性和值,并阻止添加新属性. var emptyObject = Object.freeze({}); // th ...
随机推荐
- 深入理解ES6之—set与map
Set是无重复值的有序列表.Set会自动移除重复的值,因此你可以使用它来过滤数组中重复的值并返回结果. Map是有序的键值对,其中的键允许是任何类型. Set和Map是es6新增的两个数据集合. Se ...
- C#里调用 MysqlDB
最近在做项目,发现在使用Mysql提供给C#操作的类不是和好用,就想办法写了一个操作方便的Mysql数据层类. 比如以前在执 行一个查询 代码 ...
- web前端-----JAVA Script(一)
JavaScript概述 JavaScript的历史 1992年Nombas开发出C-minus-minus(C--)的嵌入式脚本语言(最初绑定在CEnvi软件中).后将其改名ScriptEase ...
- Android 安全加密
Android 安全加密 引言 对称加密.非对称加密.消息摘要.数字签名等知识都是为了理解数字证书工作原理而作为一个预备知识.数字证书是密码学里的终极武器,是人类几千年历史总结的智慧的结晶,只 ...
- [转]如何查询SQL Server连接数
1.获取SQL Server允许同时用户连接的最大数 SELECT @@MAX_CONNECTIONS 2.获取当前指定数据库的连接信息 SELECT * FROM master.dbo.syspro ...
- Linux 性能搜集【linux_reports-cpu/memory/disks/network】
为方便问题发生后,问题原因的分析排查,我们可以在服务器中事先部署如下脚本,方便故障发生后,问题原因的分析排查 脚本部署方法: 1.将脚本[linux_reports.sh]上传到服务器 2.登陆虚拟机 ...
- Zabbix 3.0 从入门到精通(zabbix使用详解)
第1章 zabbix监控 1.1 为什么要监控 在需要的时刻,提前提醒我们服务器出问题了 当出问题之后,可以找到问题的根源 网站/服务器 的可用性 1.1.1 网站可用性 在软件系统的高可靠性(也 ...
- alex python of day2
模块 sys模块:sys模块是用c语言写的,所以在lib下是不会有sys.py这个文件存在 1 import sys 2 print(sys.path) #打印环境变量 3 print(sys.a ...
- open_basedir restriction in effect.文件访问没有权限异常
本次搭建公司web开发环境遇到了不少坑首先认为可能是设置的目录写入权限问题,后面解决了发现不是 问题在于nginx配置文件进行了网站目录的保护 配置文件/usr/local/nginx/conf/fa ...
- [最短路][模版]P1346 电车
题目描述 在一个神奇的小镇上有着一个特别的电车网络,它由一些路口和轨道组成,每个路口都连接着若干个轨道,每个轨道都通向一个路口(不排除有的观光轨道转一圈后返回路口的可能).在每个路口,都有一个开关决定 ...