Skip to content

Commit 027229c

Browse files
committed
chore(ci): blog sync
1 parent 91dde4c commit 027229c

File tree

3 files changed

+477
-0
lines changed

3 files changed

+477
-0
lines changed
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
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

Comments
 (0)