当模板 template 经过 parse 过程后,会输出生成 AST 树,那么接下来就需要对这颗树做优化
为什么要有优化过程,因为我们知道 Vue 是数据驱动,是响应式的,但是我们的模板并不是所有数据都是响应式的,也有很多数据是首次渲染后就永远不会变化的,那么这部分数据生成的 DOM 也不会变化,我们可以在 patch 的过程跳过对他们的比对
optimize方法定义在 src/compiler/optimizer.js 中

  1. /**
  2. * Goal of the optimizer: walk the generated template AST tree
  3. * and detect sub-trees that are purely static, i.e. parts of
  4. * the DOM that never needs to change.
  5. *
  6. * Once we detect these sub-trees, we can:
  7. *
  8. * 1. Hoist them into constants, so that we no longer need to
  9. * create fresh nodes for them on each re-render;
  10. * 2. Completely skip them in the patching process.
  11. * 在编译阶段可以把一些AST节点优化成静态节点
  12. * 1.markStatic(root)标记静态节点
  13. * 2.markStaticRoots(root, false)标记静态根
  14. */
  15. export function optimize (root: ?ASTElement, options: CompilerOptions) {
  16. if (!root) return
  17. isStaticKey = genStaticKeysCached(options.staticKeys || '')
  18. isPlatformReservedTag = options.isReservedTag || no
  19. // first pass: mark all non-static nodes.
  20. // 标记静态节点
  21. markStatic(root)
  22. // second pass: mark static roots.
  23. // 标记静态根
  24. markStaticRoots(root, false)
  25. }
  26. function genStaticKeys (keys: string): Function {
  27. return makeMap(
  28. 'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap' +
  29. (keys ? ',' + keys : '')
  30. )
  31. }

标记静态节点

  1. function markStatic (node: ASTNode) {
  2. node.static = isStatic(node) // 判断一个AST元素节点是否是静态
  3. if (node.type === 1) { // 节点是普通元素
  4. // do not make component slot content static. this avoids
  5. // 1. components not able to mutate slot nodes
  6. // 2. static slot content fails for hot-reloading
  7. if (
  8. !isPlatformReservedTag(node.tag) &&
  9. node.tag !== 'slot' &&
  10. node.attrsMap['inline-template'] == null
  11. ) {
  12. return
  13. }
  14. // 遍历它所有children
  15. for (let i = 0, l = node.children.length; i < l; i++) {
  16. const child = node.children[i]
  17. // 递归执行markStatic
  18. markStatic(child)
  19. if (!child.static) {
  20. node.static = false
  21. }
  22. }
  23. // 所有的elseif和else节点都不在children中, 如果节点的ifConditions不为空,则遍历ifConditions拿到所有条件中的block,也就是它们对应的AST节点,递归执行markStatic
  24. // 在这些递归过程中,一旦子节点有不是static的情况,则它的父节点的static均变成false
  25. if (node.ifConditions) {
  26. for (let i = 1, l = node.ifConditions.length; i < l; i++) {
  27. const block = node.ifConditions[i].block
  28. markStatic(block)
  29. if (!block.static) {
  30. node.static = false
  31. }
  32. }
  33. }
  34. }
  35. }

isStatic方法

  1. // 判断一个AST元素节点是否是静态
  2. function isStatic (node: ASTNode): boolean {
  3. // 表达式 非静态
  4. if (node.type === 2) { // expression
  5. return false
  6. }
  7. // 纯文本 静态
  8. if (node.type === 3) { // text
  9. return true
  10. }
  11. // 普通元素
  12. return !!(node.pre || ( // 有pre属性,那么它使用了v-pre指令 是静态
  13. // 没有使用v-if、v-for等指令(不包括v-once)
  14. // 非内置组件,是平台保留的标签
  15. // 非带有v-for的template标签的直接子节点
  16. // 节点所有属性的key都满足静态key
  17. // 以上都满足则这个AST节点是一个静态节点
  18. !node.hasBindings && // no dynamic bindings
  19. !node.if && !node.for && // not v-if or v-for or v-else
  20. !isBuiltInTag(node.tag) && // not a built-in
  21. isPlatformReservedTag(node.tag) && // not a component
  22. !isDirectChildOfTemplateFor(node) &&
  23. Object.keys(node).every(isStaticKey)
  24. ))
  25. }

标记静态根

  1. function markStaticRoots (node: ASTNode, isInFor: boolean) {
  2. if (node.type === 1) {
  3. // 对于已经是static的节点或者是v-once指令的节点,node.staticInFor=isInFor
  4. if (node.static || node.once) {
  5. node.staticInFor = isInFor
  6. }
  7. // For a node to qualify as a static root, it should have children that
  8. // are not just static text. Otherwise the cost of hoisting out will
  9. // outweigh the benefits and it's better off to just always render it fresh.
  10. // 对于 staticRoot 的判断逻辑,从注释中可以看到,对于有资格成为 staticRoot 的节点,除了本身是一个静态节点外,必须满足拥有 children,并且 children 不能只是一个文本节点,不然的话把它标记成静态根节点的收益就很小了
  11. if (node.static && node.children.length && !(
  12. node.children.length === 1 &&
  13. node.children[0].type === 3
  14. )) {
  15. node.staticRoot = true
  16. return
  17. } else {
  18. node.staticRoot = false
  19. }
  20. // 遍历children以及ifConditions,递归执行markStaticRoots
  21. if (node.children) {
  22. for (let i = 0, l = node.children.length; i < l; i++) {
  23. markStaticRoots(node.children[i], isInFor || !!node.for)
  24. }
  25. }
  26. if (node.ifConditions) {
  27. for (let i = 1, l = node.ifConditions.length; i < l; i++) {
  28. markStaticRoots(node.ifConditions[i].block, isInFor)
  29. }
  30. }
  31. }
  32. }

经过 optimize 后,AST 树变成了如下

  1. ast = {
  2. 'type': 1,
  3. 'tag': 'ul',
  4. 'attrsList': [],
  5. 'attrsMap': {
  6. ':class': 'bindCls',
  7. 'class': 'list',
  8. 'v-if': 'isShow'
  9. },
  10. 'if': 'isShow',
  11. 'ifConditions': [{
  12. 'exp': 'isShow',
  13. 'block': // ul ast element
  14. }],
  15. 'parent': undefined,
  16. 'plain': false,
  17. 'staticClass': 'list',
  18. 'classBinding': 'bindCls',
  19. 'static': false,
  20. 'staticRoot': false,
  21. 'children': [{
  22. 'type': 1,
  23. 'tag': 'li',
  24. 'attrsList': [{
  25. 'name': '@click',
  26. 'value': 'clickItem(index)'
  27. }],
  28. 'attrsMap': {
  29. '@click': 'clickItem(index)',
  30. 'v-for': '(item,index) in data'
  31. },
  32. 'parent': // ul ast element
  33. 'plain': false,
  34. 'events': {
  35. 'click': {
  36. 'value': 'clickItem(index)'
  37. }
  38. },
  39. 'hasBindings': true,
  40. 'for': 'data',
  41. 'alias': 'item',
  42. 'iterator1': 'index',
  43. 'static': false,
  44. 'staticRoot': false,
  45. 'children': [
  46. 'type': 2,
  47. 'expression': '_s(item)+":"+_s(index)'
  48. 'text': '{{item}}:{{index}}',
  49. 'tokens': [
  50. {'@binding':'item'},
  51. ':',
  52. {'@binding':'index'}
  53. ],
  54. 'static': false
  55. ]
  56. }]
  57. }

发现每一个 AST 元素节点都多了 staic 属性,并且 type 为 1 的普通元素 AST 节点多了 staticRoot 属性

通过 optimize 我们把整个 AST 树中的每一个 AST 元素节点标记了 static 和 staticRoot,它会影响接下来执行代码生成的过程