大白话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 ...
随机推荐
- 入门到熟练-Eclipse开发工具
1. 概述 本文用于Eclipse说明开发功能的各种配置.希望可以帮助到对于Eclipse工具设置不同熟练的朋友,快速上手Eclipse开发工具. 2. Eclipse的配置 2.1. 设置Eclip ...
- java 冒泡排序与选择排序
//冒泡排序 package test;public class Maopaosort { public static void method(){ int[] arr=new int[10]; ...
- WebSocket小插件
一.WebSocket小介绍 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信 ...
- 教你如何实现微信小程序与.net core应用服务端的无状态身份验证
随着.net core2的发布,越来越多人使用.net core2开发各种应用服务端,下面我就结合自己最近开发的一款小程序,给大家分享下,怎么使用小程序登录后,小程序与服务端交互的权限控制. .net ...
- [转载] zookeeper应用——集中配置管理系统的实现
转载自http://blog.csdn.net/huangfengxiao/article/details/8844239 1 场景描述 参考http://www.ibm.com/developerw ...
- 【Win 10 应用开发】UI Composition 札记(六):动画
动画在 XAML 中也有,而且基本上与 WPF 中的用法一样.不过,在 UWP 中,动画还有一种表现方式—— 通过 UI Composition 来创建. 基于 UI Composition 的动画, ...
- 在64位Win7环境+64位JDK下,运行64位Eclipse,提示“Failed to load the JNI shared library”错误,提示jvm.dll不对
-startup plugins/org.eclipse.equinox.launcher_1.2.0.v20110502.jar --launcher.library plugins/org.ecl ...
- python学习笔记 tuple
1. ()去声明.与list类似,但是其元素不能改变. 2. 需要注意的是1中的不能改变是指()中的元素不能改变,如果其元素是一个list,那么list中的元素是可以改变的,不论是大小还是其他的. 3 ...
- linux centos ubentu安装IPython四种方法
IPython是Python的交互式Shell,提供了代码自动补完,自动缩进,高亮显示,执行Shell命令等非常有用的特性.特别是它的代码补完功能,例如:在输入zlib.之后按下Tab键,IPytho ...
- selenium 执行js,实现滚动条
今天在写脚本的时候,学习了执行js,实现滚动条,对于scrollTop=10000中这个10000是怎么来的,还不是很了解,先将方法记录一下, 1.滚动条回到顶部: js_up="docum ...