本来以为 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 节点定义type1 表示元素,type2 表示插值表达式,type3 表示普通文本。可以看到,在标记 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)
}

也就是之前说的三步走:

  1. 将 html 模板解析成抽象语法树(AST)。
  2. 对 AST 做优化处理。
  3. 根据 AST 生成 render 函数。

小结

Vue 首先根据使用者的传参获得待编译的模板片段,然后使用正则匹配对片段里的标签各个击破,用步步蚕食的方法将整块模板片段最终解析成一棵 AST。获得 AST 后,后续的处理就非常方便了。Vue 会首先优化这棵 AST,将其中的静态子树找出来,这些静态节点在之后的视图更新和数据计算中是可以忽略掉的,从而提高性能。最后 Vue 遍历这棵 AST 的每个节点,根据节点的类型生成不同的函数碎片,最后拼接成整个 render 函数。

值得一提的是,将 AST 作为编译的中间形式是非常方便的,当 AST 构建出来之后,使用树形结构的深度优先遍历算法就可以方便地对树的每一个节点做处理。Vue 最后生成 render 函数也是通过遍历 AST ,根据每个节点生成函数的一小部分,最后拼接成整个函数。

本篇完,Vue 编译器部分到此完结。将在下篇开始进行 Vue 运行时相关的代码剖析,运行时这部分代码应该会是非常有意思的,包括 Vue 对象实例化,双向绑定,虚拟 DOM 等内容。

大白话 Vue 源码系列目录

本系列会以每周一篇的速度持续更新,喜欢的小伙伴记得点关注哦。

大白话Vue源码系列(03):生成render函数的更多相关文章

  1. 大白话Vue源码系列(03):生成AST

    阅读目录 AST 节点定义 标签的正则匹配 解析用到的工具方法 解析开始标签 解析结束标签 解析文本 解析整块 HTML 模板 未提及的细节 本篇探讨 Vue 根据 html 模板片段构建出 AST ...

  2. 大白话Vue源码系列(04):生成render函数

    阅读目录 优化 AST 生成 render 函数 小结 本来以为 Vue 的编译器模块比较好欺负,结果发现并没有那么简单.每一种语法指令都要考虑到,处理起来相当复杂.上篇已经生成了 AST,本篇依然对 ...

  3. 大白话Vue源码系列(02):编译器初探

    阅读目录 编译器代码藏在哪 Vue.prototype.$mount 构建 AST 的一般过程 Vue 构建的 AST 题接上文,上回书说到,Vue 的编译器模块相对独立且简单,那咱们就从这块入手,先 ...

  4. 大白话Vue源码系列(05):运行时鸟瞰图

    阅读目录 Vue 实例的生命周期 实例创建 响应的数据绑定 挂载到 DOM 节点 结论 研究 runtime 一边 Vue 一边源码 初看 Vue 是 Vue 源码是源码 再看 Vue 不是 Vue ...

  5. 大白话Vue源码系列(01):万事开头难

    阅读目录 Vue 的源码目录结构 预备知识 先捡软的捏 Angular 是 Google 亲儿子,React 是 Facebook 小正太,那咱为啥偏偏选择了 Vue 下手,一句话,Vue 是咱见过的 ...

  6. 大白话Vue源码系列目录

    .first-level{ font-size: 1.2rem; cursor: default; color: #666; } .second-level{ font-size: 1.1rem; p ...

  7. 手牵手,从零学习Vue源码 系列一(前言-目录篇)

    系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 手牵手,从零学习Vue源码 系列三(虚拟DOM篇) 陆续更新中... 预计八月中旬更新 ...

  8. 手牵手,从零学习Vue源码 系列二(变化侦测篇)

    系列文章: 手牵手,从零学习Vue源码 系列一(前言-目录篇) 手牵手,从零学习Vue源码 系列二(变化侦测篇) 陆续更新中... 预计八月中旬更新完毕. 1 概述 Vue最大的特点之一就是数据驱动视 ...

  9. 【Vue】VUE源码中的一些工具函数

    Vue源码-工具方法 /* */ //Object.freeze()阻止修改现有属性的特性和值,并阻止添加新属性. var emptyObject = Object.freeze({}); // th ...

随机推荐

  1. EF动态拼接查询

    1.业务中遇到个问题,需要查询如某表的id为1或者2或者3,这里是根据传递参数获取如:传递1,2或者1,3或者1,2,3这里在sql中很好拼接如下: or id= or name=3//3代表另一个字 ...

  2. Golang源码探索(二) 协程的实现原理

    Golang最大的特色可以说是协程(goroutine)了, 协程让本来很复杂的异步编程变得简单, 让程序员不再需要面对回调地狱, 虽然现在引入了协程的语言越来越多, 但go中的协程仍然是实现的是最彻 ...

  3. 二叉搜索树 (BST) 的创建以及遍历

    二叉搜索树(Binary Search Tree) : 属于二叉树,其中每个节点都含有一个可以比较的键(如需要可以在键上关联值), 且每个节点的键都大于其左子树中的任意节点而小于右子树的任意节点的键. ...

  4. [Bayesian] “我是bayesian我怕谁”系列 - Gaussian Process

    科班出身,贝叶斯护体,正本清源,故拿”九阳神功“自比,而非邪气十足的”九阴真经“: 现在看来,此前的八层功力都为这第九层作基础: 本系列第九篇,助/祝你早日hold住神功第九重,加入血统纯正的人工智能 ...

  5. Markdown规则

    第一次写随想,写的不好还请包涵呀!!! 这两天在用markdown写一些文档,感觉还不错,整理一下其规则,对于新手会有帮助. 1. 文章的标题,一般写在首行,第二行添加"---------- ...

  6. 走进Spark生态圈:环境的安装与配置

    什么是Spark? Apache Spark 是一种大规模数据处理的快速通用引擎,使用基于内存的处理方式,较与MapReduce而言,解决了其shuffle多次IO操作带来的效率低问题,从而达到快速的 ...

  7. Bean property属性说明

                                  来自为知笔记(Wiz)

  8. python线程池实现多线程

    参考文献 http://www.open-open.com/news/view/1c0179b http://blog.jobbole.com/52060/ 按照这个博客,实现获取多台服务器的空间使用 ...

  9. Java多线程学习之ThreadLocal源码分析

    0.概述 ThreadLocal,即线程本地变量,是一个以ThreadLocal对象为键.任意对象为值的存储结构.它可以将变量绑定到特定的线程上,使每个线程都拥有改变量的一个拷贝,各线程相同变量间互不 ...

  10. 深度学习的异构加速技术(一):AI 需要一个多大的“心脏”?

    欢迎大家前往腾讯云社区,获取更多腾讯海量技术实践干货哦~ 作者:kevinxiaoyu,高级研究员,隶属腾讯TEG-架构平台部,主要研究方向为深度学习异构计算与硬件加速.FPGA云.高速视觉感知等方向 ...