大白话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 ...
随机推荐
- 阿里云RDS SQL Server 2008 R2 使用本地SQL备份文件还原全过程
最近公司准备全面转向阿里云,写了好几个方案,最终决定购买一台ECS和一台RDS搭配使用.开始对阿里的RDS产品陌生,加上公司的数据库文件近20G,诸多担心,生怕产品买来了不能用,给公司造成损失.后来联 ...
- Oracle创建表空间、用户、分配权限语句
--创建表空间 create tablespace 表空间名字 logging datafile 'E:\app\sinohuarui\oradata\orcl\文件名.dbf' size 50m a ...
- 解决Webstom 2017中,输入法候选框无法显示问题
一.问题: 如题,IDE编辑界面内,输入法的候选框没法显示,有时需要打中文注释,非常麻烦. 原因:IDE自带的OpenJDK与输入法存在冲突 二.解决: (1)在编辑界面,双shift,搜索:swit ...
- tornado超时机制
1.https://blog.lzhaohao.info/archive/build-a-comet-application-using-tornado-and-nginx/ 2.http://qin ...
- 数据库中File权限的危害
The FILE privilege gives you permission to read and write files on the server host using the LOAD DA ...
- 网页如何展示PPT文档
最近再做一个新项目,其中有一个难点,就是如何在网页上展示PPT,我网上找到了几种方法,但是真正符合我目前这个项目的就只有这一种方法了, 使用PowerPoint to Flash将ppt文 ...
- 实现基于Keepalived主从高可用集群网站架构
背景 上一期我们实现了基于lvs负载均衡集群的电商网站架构,随着业务的发展,网站的访问量越来越大,网站访问量已经从原来的1000QPS,变为3000QPS,目前业务已经通过集群LVS架构可做到随时拓展 ...
- Mac下Kali虚拟机与宿主机共享文件夹
宿主机: 1.创建文件夹.测试文件 ZhangSan-MacBook-Air:~ zhangsan$ mkdir kalishare && cd kalishare/ ZhangSan ...
- Sublime Text 3 修改配色方案
你可能会觉得 Sublime Text 配色方案的颜色(注释.背景色)看起来不习惯,其他都满意.此时我们可以自己修改这些配色,不需要更换整个配色方案. 需要安装 PackageResourceView ...
- 1455:An Easy Problem
传送门:http://noi.openjudge.cn/ch0406/1455/ /-24作业 //#include "stdafx.h" #include<bits/std ...