• optimize
    • 标记静态节点
    • 标记静态根
    • 总结

    optimize

    当我们的模板 template 经过 parse 过程后,会输出生成 AST 树,那么接下来我们需要对这颗树做优化,optimize 的逻辑是远简单于 parse 的逻辑,所以理解起来会轻松很多。

    为什么要有优化过程,因为我们知道 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. */
    12. export function optimize (root: ?ASTElement, options: CompilerOptions) {
    13. if (!root) return
    14. isStaticKey = genStaticKeysCached(options.staticKeys || '')
    15. isPlatformReservedTag = options.isReservedTag || no
    16. // first pass: mark all non-static nodes.
    17. markStatic(root)
    18. // second pass: mark static roots.
    19. markStaticRoots(root, false)
    20. }
    21. function genStaticKeys (keys: string): Function {
    22. return makeMap(
    23. 'type,tag,attrsList,attrsMap,plain,parent,children,attrs' +
    24. (keys ? ',' + keys : '')
    25. )
    26. }

    我们在编译阶段可以把一些 AST 节点优化成静态节点,所以整个 optimize 的过程实际上就干 2 件事情,markStatic(root) 标记静态节点 ,markStaticRoots(root, false) 标记静态根。

    标记静态节点

    1. function markStatic (node: ASTNode) {
    2. node.static = isStatic(node)
    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. for (let i = 0, l = node.children.length; i < l; i++) {
    15. const child = node.children[i]
    16. markStatic(child)
    17. if (!child.static) {
    18. node.static = false
    19. }
    20. }
    21. if (node.ifConditions) {
    22. for (let i = 1, l = node.ifConditions.length; i < l; i++) {
    23. const block = node.ifConditions[i].block
    24. markStatic(block)
    25. if (!block.static) {
    26. node.static = false
    27. }
    28. }
    29. }
    30. }
    31. }
    32. function isStatic (node: ASTNode): boolean {
    33. if (node.type === 2) { // expression
    34. return false
    35. }
    36. if (node.type === 3) { // text
    37. return true
    38. }
    39. return !!(node.pre || (
    40. !node.hasBindings && // no dynamic bindings
    41. !node.if && !node.for && // not v-if or v-for or v-else
    42. !isBuiltInTag(node.tag) && // not a built-in
    43. isPlatformReservedTag(node.tag) && // not a component
    44. !isDirectChildOfTemplateFor(node) &&
    45. Object.keys(node).every(isStaticKey)
    46. ))
    47. }

    首先执行 node.static = isStatic(node)

    isStatic 是对一个 AST 元素节点是否是静态的判断,如果是表达式,就是非静态;如果是纯文本,就是静态;对于一个普通元素,如果有 pre 属性,那么它使用了 v-pre 指令,是静态,否则要同时满足以下条件:没有使用 v-ifv-for,没有使用其它指令(不包括 v-once),非内置组件,是平台保留的标签,非带有 v-fortemplate 标签的直接子节点,节点的所有属性的 key 都满足静态 key;这些都满足则这个 AST 节点是一个静态节点。

    如果这个节点是一个普通元素,则遍历它的所有 children,递归执行 markStatic。因为所有的 elseifelse 节点都不在 children 中, 如果节点的 ifConditions 不为空,则遍历 ifConditions 拿到所有条件中的 block,也就是它们对应的 AST 节点,递归执行 markStatic。在这些递归过程中,一旦子节点有不是 static 的情况,则它的父节点的 static 均变成 false。

    标记静态根

    1. function markStaticRoots (node: ASTNode, isInFor: boolean) {
    2. if (node.type === 1) {
    3. if (node.static || node.once) {
    4. node.staticInFor = isInFor
    5. }
    6. // For a node to qualify as a static root, it should have children that
    7. // are not just static text. Otherwise the cost of hoisting out will
    8. // outweigh the benefits and it's better off to just always render it fresh.
    9. if (node.static && node.children.length && !(
    10. node.children.length === 1 &&
    11. node.children[0].type === 3
    12. )) {
    13. node.staticRoot = true
    14. return
    15. } else {
    16. node.staticRoot = false
    17. }
    18. if (node.children) {
    19. for (let i = 0, l = node.children.length; i < l; i++) {
    20. markStaticRoots(node.children[i], isInFor || !!node.for)
    21. }
    22. }
    23. if (node.ifConditions) {
    24. for (let i = 1, l = node.ifConditions.length; i < l; i++) {
    25. markStaticRoots(node.ifConditions[i].block, isInFor)
    26. }
    27. }
    28. }
    29. }

    markStaticRoots 第二个参数是 isInFor,对于已经是 static 的节点或者是 v-once 指令的节点,node.staticInFor = isInFor。接着就是对于 staticRoot 的判断逻辑,从注释中我们可以看到,对于有资格成为 staticRoot 的节点,除了本身是一个静态节点外,必须满足拥有 children,并且 children 不能只是一个文本节点,不然的话把它标记成静态根节点的收益就很小了。

    接下来和标记静态节点的逻辑一样,遍历 children 以及 ifConditions,递归执行 markStaticRoots

    回归我们之前的例子,经过 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 树,去检测它的每一颗子树是不是静态节点,如果是静态节点则它们生成 DOM 永远不需要改变,这对运行时对模板的更新起到极大的优化作用。

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

    原文: https://ustbhuangyi.github.io/vue-analysis/compile/optimize.html