|
| 1 | +--- |
| 2 | + title: Vue源码——模版编译(六) |
| 3 | + date: 2023-12-23T12:38:42Z |
| 4 | + summary: |
| 5 | + tags: [] |
| 6 | +--- |
| 7 | + |
| 8 | + ## 前言 |
| 9 | +在上篇文章中我们完成了模板到 ast 抽象树的编译,得到了想要的对象后,我们就可以直接生成`render`函数了,但 Vue 对性能要求是比较高的,所以在两个阶段中间还有一个进行了一个优化的过程。 |
| 10 | +怎么实现的这个优化呢? |
| 11 | +就是本篇文章要讲的——标记静态的节点,得到`render`之后,通过其渲染得到与编译前对应的`VNode`,之后通过`patch`函数进行修补,后完成渲染。我们知道`vnode`对比真实dom的区别是发生改变时不会发生整个节点的变化,而是对比旧节点和新节点进行部分的`patch`修改。 |
| 12 | +但是在这个过程之前,需要提前知道的是有需要用这个`patch`函数的只有部分的<u>非静态标签</u>,不会变化的<u>静态标签</u>是不需要进行`patch`的,在这里的主流程就是对节点是否为静态打上标记: |
| 13 | +``` |
| 14 | +@ src/compiler/index |
| 15 | +export const createCompiler = createCompilerCreator(function baseCompile( |
| 16 | + template: string, |
| 17 | + options: CompilerOptions |
| 18 | +): CompiledResult { |
| 19 | + const ast = parse(template.trim(), options); |
| 20 | + // 进入优化阶段 |
| 21 | + if (options.optimize !== false) { |
| 22 | + optimize(ast, options); |
| 23 | + } |
| 24 | +
|
| 25 | + const code = generate(ast, options); |
| 26 | + return { |
| 27 | + ast, |
| 28 | + render: code.render, |
| 29 | + staticRenderFns: code.staticRenderFns, |
| 30 | + }; |
| 31 | +}); |
| 32 | +``` |
| 33 | + |
| 34 | +在开头声明了一个函数,又是熟悉的的缓存函数 `cached`,`makeMap` 函数前文也有提到 <a href="https://blog.liuji.site/detail/1070" target="_blank">Vue 源码中的工具函数</a>,此处的作用是返回一个用于判断当前 `map` 是否包含传入的参数 `key`,这些 `key` 是所有静态节点必须有的 `key`: |
| 35 | +``` |
| 36 | +const genStaticKeysCached = cached(genStaticKeys) |
| 37 | +
|
| 38 | +function genStaticKeys (keys: string): Function { |
| 39 | + return makeMap( |
| 40 | + 'type,tag,attrsList,attrsMap,plain,parent,children,attrs,start,end,rawAttrsMap' + |
| 41 | + (keys ? ',' + keys : '') |
| 42 | + ) |
| 43 | +} |
| 44 | +``` |
| 45 | +我们继续来看这个优化函数,传入的参数是模板编译器生成的ast对象和模板的配置对象: |
| 46 | +``` |
| 47 | +export function optimize (root: ?ASTElement, options: CompilerOptions) { |
| 48 | + // 确认是否为根元素 |
| 49 | + if (!root) return |
| 50 | + isStaticKey = genStaticKeysCached(options.staticKeys || '') |
| 51 | + // 是否为svg |
| 52 | + isPlatformReservedTag = options.isReservedTag || no |
| 53 | + // 首先标记所有的非静态节点 |
| 54 | + markStatic(root) |
| 55 | + // 标记静态根节点 |
| 56 | + markStaticRoots(root, false) |
| 57 | +} |
| 58 | +``` |
| 59 | +## 标记静态节点 |
| 60 | + |
| 61 | +Vue 通过递归判断属性来标记静态节点,回顾一下之前的编译,在ast对象中存在着一个中有一个 type,这个 type 记录着当前节点的类型,对应关系如下: |
| 62 | +| type取值 | 含义 | |
| 63 | +| - | - | |
| 64 | +| 1 | 普通的元素节点 | |
| 65 | +| 2 | 包含插值语法的动态节点 | |
| 66 | +| 3 | 纯文本节点 | |
| 67 | +``` |
| 68 | +function markStatic (node: ASTNode) { |
| 69 | + // 判断是否为静态节点 |
| 70 | + node.static = isStatic(node) |
| 71 | + if (node.type === 1) { |
| 72 | + // 排除组件——组件不会被认为是一个静态的节点 |
| 73 | + if ( |
| 74 | + !isPlatformReservedTag(node.tag) && |
| 75 | + node.tag !== 'slot' && |
| 76 | + node.attrsMap['inline-template'] == null |
| 77 | + ) { |
| 78 | + return |
| 79 | + } |
| 80 | + for (let i = 0, l = node.children.length; i < l; i++) { |
| 81 | + const child = node.children[i] |
| 82 | + markStatic(child) |
| 83 | + if (!child.static) { |
| 84 | + node.static = false |
| 85 | + } |
| 86 | + } |
| 87 | + if (node.ifConditions) { |
| 88 | + for (let i = 1, l = node.ifConditions.length; i < l; i++) { |
| 89 | + const block = node.ifConditions[i].block |
| 90 | + markStatic(block) |
| 91 | + if (!block.static) { |
| 92 | + node.static = false |
| 93 | + } |
| 94 | + } |
| 95 | + } |
| 96 | + } |
| 97 | +} |
| 98 | +``` |
| 99 | +从上文可以看到,首先通过 `isStatic` 函数判断是否为静态节点,如果是返回 `true`,反之则返回 `false`,函数实现如下: |
| 100 | +``` |
| 101 | +function isStatic (node: ASTNode): boolean { |
| 102 | + // 先通过 type 判断 |
| 103 | + if (node.type === 2) { // expression |
| 104 | + return false |
| 105 | + } |
| 106 | + if (node.type === 3) { // text |
| 107 | + return true |
| 108 | + } |
| 109 | + // 如果为 type 为 1,则进一步通过判断其属性来确认 |
| 110 | + return !!(node.pre || ( |
| 111 | + !node.hasBindings && // no dynamic bindings |
| 112 | + !node.if && !node.for && // not v-if or v-for or v-else |
| 113 | + !isBuiltInTag(node.tag) && // not a built-in |
| 114 | + isPlatformReservedTag(node.tag) && // not a component |
| 115 | + !isDirectChildOfTemplateFor(node) && |
| 116 | + Object.keys(node).every(isStaticKey) |
| 117 | + )) |
| 118 | +} |
| 119 | +``` |
| 120 | +如果元素为一个普通的元素节点(type为1),那么要确认他是一个静态节点必须要满足下面的条件: |
| 121 | ++ 元素节点使用了`v-pre`指令 |
| 122 | ++ 如果没有使用该属性,那么元素必须同时满足以下条件: |
| 123 | + - 不能是动态绑定的元素或者插槽,即标签上不能存在 `v-`、`@`、`:`、`#` |
| 124 | + - 不能是使用 `v-if`,`v-for`,`v-else`指令的元素 |
| 125 | + - 不能是内置的组件,即标签名为 `slot` 或 `component` |
| 126 | + - 不能是带有 `v-for` 的 `template` 标签 |
| 127 | + - 必须有静态节点的 key:`type`,`tag`,`attrsList`,`attrsMap`,`plain`,`parent`,`children`,`attrs`,`start`,`end`,`rawAttrsMap` |
| 128 | +遍历递归子节点进行标记,值得注意的是这里有一个`if`判断,首先我们知道的是这儿的递归是从上往下进行遍历的,那么如果父节点被判断为一个静态节点,那么它的内部应该也是静态的,而如果它的字节点是个动态节点,就自相矛盾了,为了避免这种情况,在一个子元素为动态节点的时候,他的父元素也会被重置为一个动态的节点: |
| 129 | +``` |
| 130 | +for (let i = 0, l = node.children.length; i < l; i++) { |
| 131 | + const child = node.children[i] |
| 132 | + markStatic(child) |
| 133 | + if (!child.static) { |
| 134 | + node.static = false |
| 135 | + } |
| 136 | +} |
| 137 | +``` |
| 138 | +同样的,判断`if`条件里的节点,跟上面差不多,这里就不多赘述: |
| 139 | +``` |
| 140 | +if (node.ifConditions) { |
| 141 | + for (let i = 1, l = node.ifConditions.length; i < l; i++) { |
| 142 | + const block = node.ifConditions[i].block |
| 143 | + markStatic(block) |
| 144 | + if (!block.static) { |
| 145 | + node.static = false |
| 146 | + } |
| 147 | + } |
| 148 | +} |
| 149 | +``` |
| 150 | +## 标记静态根节点 |
| 151 | +判断一个节点是否为一个静态的根节点,所谓静态的根节点,即其后代中所有的节点都是静态节点: |
| 152 | +``` |
| 153 | +function markStaticRoots (node: ASTNode, isInFor: boolean) { |
| 154 | + if (node.type === 1) { |
| 155 | + if (node.static || node.once) { |
| 156 | + node.staticInFor = isInFor |
| 157 | + } |
| 158 | + // For a node to qualify as a static root, it should have children that |
| 159 | + // are not just static text. Otherwise the cost of hoisting out will |
| 160 | + // outweigh the benefits and it's better off to just always render it fresh. |
| 161 | + if (node.static && node.children.length && !( |
| 162 | + node.children.length === 1 && |
| 163 | + node.children[0].type === 3 |
| 164 | + )) { |
| 165 | + node.staticRoot = true |
| 166 | + return |
| 167 | + } else { |
| 168 | + node.staticRoot = false |
| 169 | + } |
| 170 | + if (node.children) { |
| 171 | + for (let i = 0, l = node.children.length; i < l; i++) { |
| 172 | + markStaticRoots(node.children[i], isInFor || !!node.for) |
| 173 | + } |
| 174 | + } |
| 175 | + if (node.ifConditions) { |
| 176 | + for (let i = 1, l = node.ifConditions.length; i < l; i++) { |
| 177 | + markStaticRoots(node.ifConditions[i].block, isInFor) |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | +} |
| 182 | +``` |
| 183 | +待续... |
| 184 | + |
| 185 | + |
0 commit comments