Vue源码详解之compile函数指令的提取
# Vue源码详解之compile函数指令的提取
# compile
compile阶段执行的compileRoot函数就是编译我们在transclude阶段说过的,我们分别提取到了el顶级元素的属性和模板的顶级元素的属性,如果是component,那就需要把两者分开编译生成两个link。
我们来说说compile函数,他对元素执行compileNode,对其childNodes执行compileNodeList:
export function compile (el, options, partial) {
// link function for the node itself.
var nodeLinkFn = partial || !options._asComponent
? compileNode(el, options)
: null
// link function for the childNodes
// 如果nodeLinkFn.terminal为true,说明nodeLinkFn接管了整个元素和其子元素的编译过程,那也就不用编译el.childNodes
var childLinkFn =
!(nodeLinkFn && nodeLinkFn.terminal) &&
!isScript(el) &&
el.hasChildNodes()
? compileNodeList(el.childNodes, options)
: null
return function compositeLinkFn (vm, el, host, scope, frag) {
// cache childNodes before linking parent, fix #657
var childNodes = toArray(el.childNodes)
// link
// 任何link都是包裹在linkAndCapture中执行的,详见linkAndCapture函数
var dirs = linkAndCapture(function compositeLinkCapturer () {
if (nodeLinkFn) nodeLinkFn(vm, el, host, scope, frag)
if (childLinkFn) childLinkFn(vm, childNodes, host, scope, frag)
}, vm)
return makeUnlinkFn(vm, dirs)
}
}
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
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
function compileNode (node, options) {
var type = node.nodeType
if (type === 1 && !isScript(node)) {
return compileElement(node, options)
} else if (type === 3 && node.data.trim()) {
return compileTextNode(node, options)
} else {
return null
}
}
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# compileElement
function compileElement (el, options) {
if (el.tagName === 'TEXTAREA') {
// textarea元素是把tag中间的内容当做了他的value,这和input什么的不太一样
// 因此大家写模板的时候通常是这样写: <textarea>{{hello}}</textarea>
// 但是template转换成dom之后,这个内容跑到了textarea元素的value属性上,tag中间的内容是空的,
// 因此遇到textarea的时候需要单独编译一下它的value
var tokens = parseText(el.value)
if (tokens) {
el.setAttribute(':value', tokensToExp(tokens))
el.value = ''
}
}
var linkFn
var hasAttrs = el.hasAttributes()
var attrs = hasAttrs && toArray(el.attributes)
// check terminal directives (for & if)
if (hasAttrs) {
linkFn = checkTerminalDirectives(el, attrs, options)
}
// check element directives
if (!linkFn) {
linkFn = checkElementDirectives(el, options)
}
// check component
if (!linkFn) {
linkFn = checkComponent(el, options)
}
// normal directives
if (!linkFn && hasAttrs) {
// 一般会进入到这里
linkFn = compileDirectives(attrs, options)
}
return linkFn
}
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
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
最终会生成这么一个指令描述对象,以v-bind:href.literal="mylink"
为例:
{
arg:"href",
attr:"v-bind:href.literal",
def:Object,// v-bind指令的定义
expression:"mylink", // 表达式,如果是插值的话,那主要用到的是下面的interp字段
filters:undefined
hasOneTime:undefined
interp:undefined,// 存放插值token
modifiers:Object, // literal修饰符的定义
name:"bind" //指令类型
raw:"mylink" //未处理前的原始属性值
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12