Vue源码详解之compile函数的整体介绍与transclude分析
# Vue源码详解之compile函数的整体介绍与transclude分析
# compile
Vue处理完数据和event之后就到了$mount,而$mount就是在this._compile后触发编译完成的钩子而已,所以核心就是Vue.prototype._compile。
_compile包含了Vue构建的三个阶段,transclude,compile,link。而link阶段其实是放在linkAndCapture里执行的,这里又包含了watcher的生成,指令的bind、update等操作。
我先简单讲讲什么是指令,虽然Vue文档里说的指令是v-if,v-for等这种HTML的attribute,其实在Vue内部,只要是被Vue处理的dom上的东西都是指令,比如dom内容里的,最终会转换成一个v-text的指令和一个textNode,而一个子组件
<component><component>
也会生成指令,还有slot,或者是你自己在元素上写的attribute比如hello=
也会被编译为一个v-bind指令。我们看到,基本只要是涉及dom的(不是响应式的也包含在内,只要是vue提供的功能),不管是dom标签,还是dom属性、内容,都会被处理为指令。所以不要有指令就是attribute的惯性思维。
Vue.prototype._compile = function (el) {
var options = this.$options
// transclude and init element
// transclude can potentially replace original
// so we need to keep reference; this step also injects
// the template and caches the original attributes
// on the container node and replacer node.
var original = el
el = transclude(el, options)
// 在el这个dom上挂一些参数,并触发'beforeCompile'钩子,为compile做准备
this._initElement(el)
// handle v-pre on root node (#2026)
// v-pre指令的话就什么都不用做了。
if (el.nodeType === 1 && getAttr(el, 'v-pre') !== null) {
return
}
// root is always compiled per-instance, because
// container attrs and props can be different every time.
var contextOptions = this._context && this._context.$options
var rootLinker = compileRoot(el, options, contextOptions)
// resolve slot distribution
// 具体是将各个slot存储到vm._slotContents的对应属性里面去,
// 然后后面的compile阶段会把slot解析为指令然后进行处理
resolveSlots(this, options._content)
// compile and link the rest
var contentLinkFn
var ctor = this.constructor
// component compilation can be cached
// as long as it's not using inline-template
// 这里是组件的情况才进入的,大家先忽略此段代码
if (options._linkerCachable) {
contentLinkFn = ctor.linker
if (!contentLinkFn) {
contentLinkFn = ctor.linker = compile(el, options)
}
}
// link phase
// make sure to link root with prop scope!
var rootUnlinkFn = rootLinker(this, el, this._scope)
// compile和link一并做了
var contentUnlinkFn = contentLinkFn
? contentLinkFn(this, el)
: compile(el, options)(this, el)
// register composite unlink function
// to be called during instance destruction
this._unlinkFn = function () {
rootUnlinkFn()
// passing destroying: true to avoid searching and
// splicing the directives
contentUnlinkFn(true)
}
// finally replace original
if (options.replace) {
replace(original, el)
}
this._isCompiled = true
this._callHook('compiled')
}
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# transclude函数
/**
* Process an element or a DocumentFragment based on a
* instance option object. This allows us to transclude
* a template node/fragment before the instance is created,
* so the processed fragment can then be cloned and reused
* in v-for.
*
* @param {Element} el
* @param {Object} options
* @return {Element|DocumentFragment}
*/
export function transclude (el, options) {
// extract container attributes to pass them down
// to compiler, because they need to be compiled in
// parent scope. we are mutating the options object here
// assuming the same object will be used for compile
// right after this.
if (options) {
options._containerAttrs = extractAttrs(el)
}
// for template tags, what we want is its content as
// a documentFragment (for fragment instances)
if (isTemplate(el)) {
el = parseTemplate(el)
}
if (options) {
// 如果当前是component,并且没有模板,只有一个壳
// 那么只需要处理内容的嵌入
if (options._asComponent && !options.template) {
options.template = '<slot></slot>'
}
if (options.template) {
//基本都会进入到这里
options._content = extractContent(el)
el = transcludeTemplate(el, options)
}
}
if (isFragment(el)) {
// anchors for fragment instance
// passing in `persist: true` to avoid them being
// discarded by IE during template cloning
prepend(createAnchor('v-start', true), el)
el.appendChild(createAnchor('v-end', true))
}
return el
}
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
36
37
38
39
40
41
42
43
44
45
46
47
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
36
37
38
39
40
41
42
43
44
45
46
47
/**
* Process the template option.
* If the replace option is true this will swap the $el.
*
* @param {Element} el
* @param {Object} options
* @return {Element|DocumentFragment}
*/
function transcludeTemplate (el, options) {
var template = options.template
var frag = parseTemplate(template, true)
if (frag) {
// 对于非片段实例情况且replace为true的情况下,frag的第一个子节点就是最终el元素的替代者
var replacer = frag.firstChild
var tag = replacer.tagName && replacer.tagName.toLowerCase()
if (options.replace) {
/* istanbul ignore if */
if (el === document.body) {
process.env.NODE_ENV !== 'production' && warn(
'You are mounting an instance with a template to ' +
'<body>. This will replace <body> entirely. You ' +
'should probably use `replace: false` here.'
)
}
// there are many cases where the instance must
// become a fragment instance: basically anything that
// can create more than 1 root nodes.
if (
// multi-children template
frag.childNodes.length > 1 ||
// non-element template
replacer.nodeType !== 1 ||
// single nested component
tag === 'component' ||
resolveAsset(options, 'components', tag) ||
hasBindAttr(replacer, 'is') ||
// element directive
resolveAsset(options, 'elementDirectives', tag) ||
// for block
replacer.hasAttribute('v-for') ||
// if block
replacer.hasAttribute('v-if')
) {
return frag
} else {
// 抽取replacer自带的属性,他们将在自身作用域下编译
options._replacerAttrs = extractAttrs(replacer)
// 把el的所有属性都转移到replace上面去,因为我们后面将不会再处理el直至他最后被replacer替换
mergeAttrs(el, replacer)
return replacer
}
} else {
el.appendChild(frag)
return el
}
} else {
process.env.NODE_ENV !== 'production' && warn(
'Invalid template option: ' + template
)
}
}
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
function stringToFragment (templateString, raw) {
// 缓存机制
// try a cache hit first
var cacheKey = raw
? templateString
: templateString.trim()
var hit = templateCache.get(cacheKey)
if (hit) {
return hit
}
//这三个正则分别是/<([\w:-]+)/ 和/&#?\w+?;/和/<!--/
var frag = document.createDocumentFragment()
var tagMatch = templateString.match(tagRE)
var entityMatch = entityRE.test(templateString)
var commentMatch = commentRE.test(templateString)
if (!tagMatch && !entityMatch && !commentMatch) {
// 如果没有tag 或者没有html字符实体(如 ) 或者 没有注释
// text only, return a single text node.
frag.appendChild(
document.createTextNode(templateString)
)
} else {
// 这里如前面的函数签名所说,使用了jQuery 和 component/domify中所使用的生成元素的策略
// 我们要将模板变成实际的dom元素,一个简单的方法的是创建一个div document.createElement('div')
// 然后再设置这个div的innerHtml为我们的模板,
// (不直接创建一个模板的根元素是因为模板可能是片段实例,也就会生成多个dom元素)
// (而设置这个div的outerHtml也不行哈,不能设置没有父元素的outerHtml)
// 但是许多特殊元素只能再固定的父元素下存在,不能直接存在于div下,比如tbody,tr,th,td,legend等等等等
// 那么怎么办? 所以就有了下面这个先获取第一个标签,然后按照map的里预先设置的内容,给模板设置设置好父元素,
// 把模板嵌入到合适的父元素下,然后再层层进入父元素获取真正的模板元素.
var tag = tagMatch && tagMatch[1]
var wrap = map[tag] || map.efault
var depth = wrap[0]
var prefix = wrap[1]
var suffix = wrap[2]
var node = document.createElement('div')
node.innerHTML = prefix + templateString + suffix
// 这里是不断深入,进入正确的dom,
// 比如你标签是tr,那么我会为包上table和tbody元素
// 那么我拿到你的时候应该剥开外层的两个元素,让node指到tr
while (depth--) {
node = node.lastChild
}
var child
/* eslint-disable no-cond-assign */
// 用while循环把所有的子节点都提取了,因为可能是片段实例
while (child = node.firstChild) {
/* eslint-enable no-cond-assign */
frag.appendChild(child)
}
}
if (!raw) {
trimNode(frag)
}
templateCache.put(cacheKey, frag)
return frag
}
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60