当模板 template 经过 parse 过程后,会输出生成 AST 树,那么接下来就需要对这颗树做优化
为什么要有优化过程,因为我们知道 Vue 是数据驱动,是响应式的,但是我们的模板并不是所有数据都是响应式的,也有很多数据是首次渲染后就永远不会变化的,那么这部分数据生成的 DOM 也不会变化,我们可以在 patch 的过程跳过对他们的比对
optimize方法定义在 src/compiler/optimizer.js 中
/*** Goal of the optimizer: walk the generated template AST tree* and detect sub-trees that are purely static, i.e. parts of* the DOM that never needs to change.** Once we detect these sub-trees, we can:** 1. Hoist them into constants, so that we no longer need to* create fresh nodes for them on each re-render;* 2. Completely skip them in the patching process.* 在编译阶段可以把一些AST节点优化成静态节点* 1.markStatic(root)标记静态节点* 2.markStaticRoots(root, false)标记静态根*/export function optimize (root: ?ASTElement, options: CompilerOptions) {if (!root) returnisStaticKey = genStaticKeysCached(options.staticKeys || '')isPlatformReservedTag = options.isReservedTag || no// first pass: mark all non-static nodes.// 标记静态节点markStatic(root)// second pass: mark static roots.// 标记静态根markStaticRoots(root, false)}function genStaticKeys (keys: string): Function {return makeMap('type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap' +(keys ? ',' + keys : ''))}
标记静态节点
function markStatic (node: ASTNode) {node.static = isStatic(node) // 判断一个AST元素节点是否是静态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-reloadingif (!isPlatformReservedTag(node.tag) &&node.tag !== 'slot' &&node.attrsMap['inline-template'] == null) {return}// 遍历它所有childrenfor (let i = 0, l = node.children.length; i < l; i++) {const child = node.children[i]// 递归执行markStaticmarkStatic(child)if (!child.static) {node.static = false}}// 所有的elseif和else节点都不在children中, 如果节点的ifConditions不为空,则遍历ifConditions拿到所有条件中的block,也就是它们对应的AST节点,递归执行markStatic// 在这些递归过程中,一旦子节点有不是static的情况,则它的父节点的static均变成falseif (node.ifConditions) {for (let i = 1, l = node.ifConditions.length; i < l; i++) {const block = node.ifConditions[i].blockmarkStatic(block)if (!block.static) {node.static = false}}}}}
isStatic方法
// 判断一个AST元素节点是否是静态function isStatic (node: ASTNode): boolean {// 表达式 非静态if (node.type === 2) { // expressionreturn false}// 纯文本 静态if (node.type === 3) { // textreturn true}// 普通元素return !!(node.pre || ( // 有pre属性,那么它使用了v-pre指令 是静态// 没有使用v-if、v-for等指令(不包括v-once)// 非内置组件,是平台保留的标签// 非带有v-for的template标签的直接子节点// 节点所有属性的key都满足静态key// 以上都满足则这个AST节点是一个静态节点!node.hasBindings && // no dynamic bindings!node.if && !node.for && // not v-if or v-for or v-else!isBuiltInTag(node.tag) && // not a built-inisPlatformReservedTag(node.tag) && // not a component!isDirectChildOfTemplateFor(node) &&Object.keys(node).every(isStaticKey)))}
标记静态根
function markStaticRoots (node: ASTNode, isInFor: boolean) {if (node.type === 1) {// 对于已经是static的节点或者是v-once指令的节点,node.staticInFor=isInForif (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.// 对于 staticRoot 的判断逻辑,从注释中可以看到,对于有资格成为 staticRoot 的节点,除了本身是一个静态节点外,必须满足拥有 children,并且 children 不能只是一个文本节点,不然的话把它标记成静态根节点的收益就很小了if (node.static && node.children.length && !(node.children.length === 1 &&node.children[0].type === 3)) {node.staticRoot = truereturn} else {node.staticRoot = false}// 遍历children以及ifConditions,递归执行markStaticRootsif (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)}}}}
经过 optimize 后,AST 树变成了如下
ast = {'type': 1,'tag': 'ul','attrsList': [],'attrsMap': {':class': 'bindCls','class': 'list','v-if': 'isShow'},'if': 'isShow','ifConditions': [{'exp': 'isShow','block': // ul ast element}],'parent': undefined,'plain': false,'staticClass': 'list','classBinding': 'bindCls','static': false,'staticRoot': false,'children': [{'type': 1,'tag': 'li','attrsList': [{'name': '@click','value': 'clickItem(index)'}],'attrsMap': {'@click': 'clickItem(index)','v-for': '(item,index) in data'},'parent': // ul ast element'plain': false,'events': {'click': {'value': 'clickItem(index)'}},'hasBindings': true,'for': 'data','alias': 'item','iterator1': 'index','static': false,'staticRoot': false,'children': ['type': 2,'expression': '_s(item)+":"+_s(index)''text': '{{item}}:{{index}}','tokens': [{'@binding':'item'},':',{'@binding':'index'}],'static': false]}]}
发现每一个 AST 元素节点都多了 staic 属性,并且 type 为 1 的普通元素 AST 节点多了 staticRoot 属性
通过 optimize 我们把整个 AST 树中的每一个 AST 元素节点标记了 static 和 staticRoot,它会影响接下来执行代码生成的过程
