大白话Vue源码系列(04):生成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源码系列(04):生成render函数的更多相关文章
- 大白话Vue源码系列(03):生成render函数
阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...
- 大白话Vue源码系列(03):生成AST
阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 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 ...
随机推荐
- mac电脑安装apache,不能启动
因为mac系统是自带apach的 如果安装正确还是不能启动,有可能是 mac电脑自带apache功能,与安装的apache冲突. 这样关闭Mac自带apach即可. mac电脑apache命令:重启a ...
- 动态创建 script 实现跨域请求数据
动态创建script标签 (由事件触发) 在我们需要请求数据的时候我们就可以动态的创建 script 标签 src设置为我们需要请求数据的地址 另外我们可以附加参数 ?后面附加参数 例如 :?参数=1 ...
- OpenTSDB-Querying or Reading Data
Querying or Reading Data OpenTSDB offers a number of means to extract data such as CLI tools, an HTT ...
- List实现
1.元素添加 #include <stdio.h> #include <stdlib.h> struct ListNode{ struct ListNode* next; in ...
- 预加载(图片,css ,js)
图片预加载 new Image().src = 'http://img1.t.sinajs.cn/t35/skin/skin_008/skin.css'; //新浪(4) 非ie下预加载(js,css ...
- Python 简单理解多线程
进程,是一个或多个线程的集合,每个进程在内存中是相对独立的. 线程,是计算机最小的运算单元,每个进程至少要有一个线程,多个线程时,每个线程间之间共享内存. 分别举例常规运行和多线程运行: 0)常规运行 ...
- 老男孩Python全栈开发(92天全)视频教程 自学笔记03
day3课程目录: pyhton的历史 32bit和64bit系统的区别 Python版本的选择 第一个pyhton程序 文件后缀名及系统环境变量的介绍 pyhton程序的执行和其他编程语言的简单对比 ...
- 【架构篇】OCP和依赖注入
描述 本篇文章主要讲解 : (1)OO设计OCP原则: (2)依赖注入引入 (3)依赖注入分析 (4)依赖注入种类 1 内容区 1.1 IOC背景 (1)Ralph E. Johnson &a ...
- 漫谈 SLAM 技术(上)
欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者:解洪文 导语 随着最近几年机器人.无人机.无人驾驶.VR/AR的火爆,SLAM技术也为大家熟知,被认为是这些领域的关键技术之一.本文对S ...
- CORS(跨站资源共享)介绍
起因 有同学在nginx站点配置中加了一行Access-Control-Allow-Origin *,导致微信中业务数据异常,抓包看http头有两个Access-Control-Allow-Origi ...