Vue源码解析16-v-for的处理

parse

processFor用于处理v-for指令:

1
2
3
4
5
6
7
8
9
10
export function processFor(el: ASTElement) {
let exp;
// 从attrsMap中获取key为‘v-for’的属性值,例如 "(value,key,index) in items"
if ((exp = getAndRemoveAttr(el, 'v-for'))) {
const res = parseFor(exp);
if (res) {
extend(el, res);
}
}
}

依然还是用例子来帮助理解,假设html为:

1
2
3
<div id="app">
<div v-for="(value,key,index) in items">111</div>
</div>

首先getAndRemoveAttr看名字应该能查到它是获取并删除特定的节点属性:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// note: this only removes the attr from the Array (attrsList) so that it
// doesn't get processed by processAttrs.
// By default it does NOT remove it from the map (attrsMap) because the map is
// needed during codegen.
export function getAndRemoveAttr(el: ASTElement, name: string, removeFromMap?: boolean): ?string {
let val;
// attrsMap用于快速查找有没有某个属性,attrsList用于存放所有的属性
if ((val = el.attrsMap[name]) != null) {
const list = el.attrsList;
for (let i = 0, l = list.length; i < l; i++) {
if (list[i].name === name) {
list.splice(i, 1);
break;
}
}
}
if (removeFromMap) {
delete el.attrsMap[name];
}
return val;
}

由于我们没有传入removeFromMap参数,所以节点上还是保留了v-for属性。这样我们拿到的exp就是(value,key,index) in itemsparseFor用于解析这个表达式,获取其中的各个部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
export function parseFor(exp: string): ?ForParseResult {
// 若exp为(value,key,index), inMatch为
/**
* [
* "(value,key,index) in items",
"(value,key,index)",
"items"
* ]
*/
const inMatch = exp.match(forAliasRE);
if (!inMatch) return;
const res = {};
res.for = inMatch[2].trim();
// 去掉两边括号,结果为‘value,key,index’
const alias = inMatch[1].trim().replace(stripParensRE, '');
/**
* iteratorMatch示范
* [
* ",key,index",
"key",
"index"
* ]
*/
const iteratorMatch = alias.match(forIteratorRE);
if (iteratorMatch) {
res.alias = alias.replace(forIteratorRE, ''); // 'value'
res.iterator1 = iteratorMatch[1].trim(); // 'key'
if (iteratorMatch[2]) {
res.iterator2 = iteratorMatch[2].trim(); // 'index'
}
} else {
res.alias = alias; // ‘value,key,index’
}
return res;
}

注释应该很详细,之所以有很多if判断是因为v-for的格式可以有很多种,需要针对性处理。我们的示范经过parseFor处理后拿到结果为:

1
2
3
4
5
6
{
for: 'items',
alias: 'value',
iterator1: 'key'
iterator2: 'index'
}

最终经过parse,上面的这几个属性会全部放到el这个AST节点上。

generate

src/compiler/codegen/index.js中的genFor用于处理AST节点上与v-for相关的属性,生成对应字符串:

1
2
3
4
5
6
7
8
9
10
export function genFor(el: any, state: CodegenState, altGen?: Function, altHelper?: string): string {
const exp = el.for;
const alias = el.alias;
const iterator1 = el.iterator1 ? `,${el.iterator1}` : '';
const iterator2 = el.iterator2 ? `,${el.iterator2}` : '';

el.forProcessed = true; // avoid recursion
// 示范:"_l((items),function(value,key,index){return _c('p',[_v(_s(index)+". "+_s(key)+" : "+_s(value))])})"
return `${altHelper || '_l'}((${exp}),` + `function(${alias}${iterator1}${iterator2}){` + `return ${(altGen || genElement)(el, state)}` + '})';
}

炒鸡简单有木有,我们生成的字符串如下:

1
2
3
`_l(items, function(value, key, index) {
return _c('div', [_v('111')]);
})`;

_vcreateTextVNode)用于创建一个vnode文本结点:

1
2
3
export function createTextVNode(val: string | number) {
return new VNode(undefined, undefined, undefined, String(val));
}

_c在之前讲述vnode时提过,这里再贴出来下:

1
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false);

_l(renderList)用于渲染列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Runtime helper for rendering v-for lists.
*/
export function renderList(val: any, render: (val: any, keyOrIndex: string | number, index?: number) => VNode): ?Array<VNode> {
let ret: ?Array<VNode>, i, l, keys, key;
if (Array.isArray(val) || typeof val === 'string') {
ret = new Array(val.length);
for (i = 0, l = val.length; i < l; i++) {
ret[i] = render(val[i], i);
}
} else if (typeof val === 'number') {
ret = new Array(val);
for (i = 0; i < val; i++) {
ret[i] = render(i + 1, i);
}
} else if (isObject(val)) {
keys = Object.keys(val);
ret = new Array(keys.length);
for (i = 0, l = keys.length; i < l; i++) {
key = keys[i];
ret[i] = render(val[key], key, i);
}
}
if (isDef(ret)) {
(ret: any)._isVList = true;
}
return ret;
}

参数val的形式可以有多种:字符串、数字、对象、数组,代码比较简单很快能看懂。

最终我们拿到的render字符串为:

1
2
3
4
5
6
7
8
9
10
11
`
with (this) {
return _c(
'div',
{ attrs: { id: 'app' } },
_l(items, function(value, key, index) {
return _c('div', [_v('111')]);
}),
);
}
`;

之后就是生成vnode了,v-for的痕迹就看不到了。