上一章Vue源码解析系列(六) -- 模板tamplate是如何解析编译的我们讲了tamplate
经过parse
函数解析,经过词法分析
、语法分析
生成了AST
对象,那么我们这一章就来讲讲在解析编译
过程中Vue
机制做了哪些优化
吧,我们知道Vue
就是通过optimize
函数进行静态打点
操作,以便于后面进行patch dom
我们会跳过此节点,不做比较,节省性能。我们首先进入optimizer.js
里面
export function optimize (root: ?ASTElement, options: CompilerOptions) {
if (!root) return
// staticKeys 这是被认为是静态节点的标记
isStaticKey = genStaticKeysCached(options.staticKeys || '')
isPlatformReservedTag = options.isReservedTag || no
// 标记 AST 所有静态节点
markStatic(root)
// 第二步 标记 AST 所有父节点(即子树根节点)
markStaticRoots(root, false)
}
我们直接看到optimize函数里面两个关键函数markStatic()
,markStaticRoots()
,首先我们来看一下markStatic()
函数内部的运行机制:
function markStatic (node: ASTNode) {
node.static = isStatic(node)
if (node.type === 1) {
// do not make component slot content static. this avoids
// 1. components not able to mutate slot nodes
// 2. static slot content fails for hot-reloading
if (
!isPlatformReservedTag(node.tag) &&
node.tag !== 'slot' &&
node.attrsMap['inline-template'] == null
) {
return
}
for (let i = 0, l = node.children.length; i < l; i++) {
const child = node.children[i]
markStatic(child)
if (!child.static) {
node.static = false
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
const block = node.ifConditions[i].block
markStatic(block)
if (!block.static) {
node.static = false
}
}
}
}
}
node.static
被赋值isStatic
函数的返回值,那么我们来看看isStatic
:
function isStatic (node: ASTNode): boolean {
if (node.type === 2) { // expression(插值表达式)
return false
}
if (node.type === 3) { // text(文本)
return true
}
//处理特殊字符
return !!(node.pre || (
!node.hasBindings && // no dynamic bindings
!node.if && !node.for && // not v-if or v-for or v-else
!isBuiltInTag(node.tag) && // not a built-in
isPlatformReservedTag(node.tag) && // not a component
!isDirectChildOfTemplateFor(node) &&
Object.keys(node).every(isStaticKey)
))
}
可以看出这个函数的作用就是标记所有静态节点
,分析上面ASTNode
的type
类型有三种:1为元素节点
、2为插值表达式
、3为普通文本节点
,当type
为1或者2
的时候,我们不采取措施。markStatic
函数就是递归
静态节点的子节点
是否有被isStatic
标记过得静态标记static
。
markStaticRoots
函数则是标记ASTElemet
树形结构:
function markStaticRoots (node: ASTNode, isInFor: boolean) {
if (node.type === 1) {
if (node.static || node.once) {
node.staticInFor = isInFor
}
// 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
}
if (node.children) {
for (let i = 0, l = node.children.length; i < l; i++) {
markStaticRoots(node.children[i], isInFor || !!node.for)
}
}
if (node.ifConditions) {
for (let i = 1, l = node.ifConditions.length; i < l; i++) {
markStaticRoots(node.ifConditions[i].block, isInFor)
}
}
}
}
这一段注释就非常有意思了:
// 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.
我们翻译过来就是:一个节点想要有变成静态根的资格,那么它的子节点就不应该仅仅是一个普通静态文本,否则我们把它标记起来(也就是提取出来)的成本要比直接渲染大,性能也不好
这是啥意思呢?说白了就是我们一个根节点里面,假如只有一个普通文本的时候,那么我们直接渲染
比标记
来的更快
,更高
,更远
纵观optimize
函数,就是两轮遍历:
- 遍历第一轮,标记
static
属性:判断node
是否为static
,标记node
的children
是否为static
,若不存在static
子节点,父节点更改为static = false
- 遍历第二轮,标记
staticRoot
,标记static
的节点为staticRoot
,标记节点children
的staticRoot
,为了避免过度优化,仅有静态文本
的为子节点的节点不被标记为staticRoot
, 那么到这里解析编译第二步optimize
已经搞完了,我们应该get
到Vue
这一步的核心
就是给一些静态节点打标
,对后续操作提升一些优化。当你知道后面的diff算法
是怎么样diff
的,那就会感叹这一步的必要性
。下一章我们将继续讲解编译完成AST
转为render Function Code
的故事-Vue源码解析系列(八) -- 虚拟dom是怎么样生成的