-
Notifications
You must be signed in to change notification settings - Fork 99
/
fragment.js
226 lines (207 loc) · 5.99 KB
/
fragment.js
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
import {
createAnchor,
before,
prepend,
inDoc,
mapNodeRange,
removeNodeRange
} from '../util/index'
import {
beforeWithTransition,
removeWithTransition
} from '../transition/index'
/**
* Abstraction for a partially-compiled fragment.
* Can optionally compile content with a child scope.
*
* @param {Function} linker
* @param {Vue} vm
* @param {DocumentFragment} frag
* @param {Vue} [host]
* @param {Object} [scope]
* @param {Fragment} [parentFrag]
*/
export default function Fragment (linker, vm, frag, host, scope, parentFrag) {
// 当v-for指令里每个v-for的元素里有component时,那么其便会添加到children数组中
this.children = []
this.childFrags = []
this.vm = vm
this.scope = scope
this.inserted = false
this.parentFrag = parentFrag
if (parentFrag) {
parentFrag.childFrags.push(this)
}
// 执行linker, 创建Directive实例,创建watcher,订阅scope上的数据
this.unlink = linker(vm, frag, host, scope, this)
// 举个例子如果是 <li v-for="a in b"></li>,那么就属于single的情况,即每个数组元素只会创建一个DOM节点,
// 那么这种情况下DOM的移动和销毁就简单得多,只用移动那个DOM
// 如果是<template v-for="a in b"><span>1</span><span>2</span></template>
// 那么就要意味着移动多个DOM,因此这种情况下就要创建两个anchor标识当前数组元素对应哪些DOM
// 每次移动和销毁,需要先找到两个anchor,然后将两个anchor间的DOM统一移动或删除
var single = this.single =
frag.childNodes.length === 1 &&
// do not go single mode if the only node is an anchor
// 比如v-html的情况里面的DOM只是一个anchor,
!(frag.childNodes[0].__v_anchor)
if (single) {
this.node = frag.childNodes[0]
this.before = singleBefore
this.remove = singleRemove
} else {
this.node = createAnchor('fragment-start')
this.end = createAnchor('fragment-end')
this.frag = frag
prepend(this.node, frag)
frag.appendChild(this.end)
this.before = multiBefore
this.remove = multiRemove
}
this.node.__v_frag = this
}
/**
* Call attach/detach for all components contained within
* this fragment. Also do so recursively for all child
* fragments.
*
* @param {Function} hook
*/
Fragment.prototype.callHook = function (hook) {
var i, l
for (i = 0, l = this.childFrags.length; i < l; i++) {
this.childFrags[i].callHook(hook)
}
for (i = 0, l = this.children.length; i < l; i++) {
hook(this.children[i])
}
}
/**
* Insert fragment before target, single node version
*
* @param {Node} target
* @param {Boolean} withTransition
*/
function singleBefore (target, withTransition) {
this.inserted = true
var method = withTransition !== false
? beforeWithTransition
: before
// 把this.node使用insertBefore插入到target之前
method(this.node, target, this.vm)
if (inDoc(this.node)) {
this.callHook(attach)
}
}
/**
* Remove fragment, single node version
*/
// 移除fragment,会摧毁fragment的DOM上的指令和相关watcher,并最终执行fragment.destroy()
function singleRemove () {
this.inserted = false
var shouldCallRemove = inDoc(this.node)
var self = this
// 子fragment 子component 和watcher的teardown在此完成
this.beforeRemove()
// 真正移除dom,触发detach钩子 以及最终执行fragment.destroy()再此完成
// destroy里会执行unlink,unlink闭包了link阶段生成的指令,
// 会对所有指令执行_destroy(),并从this.dirs中移除
removeWithTransition(this.node, this.vm, function () {
if (shouldCallRemove) {
self.callHook(detach)
}
self.destroy()
})
}
/**
* Insert fragment before target, multi-nodes version
*
* @param {Node} target
* @param {Boolean} withTransition
*/
function multiBefore (target, withTransition) {
this.inserted = true
var vm = this.vm
var method = withTransition !== false
? beforeWithTransition
: before
mapNodeRange(this.node, this.end, function (node) {
method(node, target, vm)
})
if (inDoc(this.node)) {
this.callHook(attach)
}
}
/**
* Remove fragment, multi-nodes version
*/
function multiRemove () {
this.inserted = false
var self = this
var shouldCallRemove = inDoc(this.node)
this.beforeRemove()
removeNodeRange(this.node, this.end, this.vm, this.frag, function () {
if (shouldCallRemove) {
self.callHook(detach)
}
self.destroy()
})
}
/**
* Prepare the fragment for removal.
*/
// 主要是执行子fragment的beforeRemove,frament内component的$destroy和相关watcher的teardown
Fragment.prototype.beforeRemove = function () {
var i, l
for (i = 0, l = this.childFrags.length; i < l; i++) {
// call the same method recursively on child
// fragments, depth-first
// 先执行子fragment的beforeRemove 如尤雨溪所述,深度优先的递归执行
this.childFrags[i].beforeRemove(false)
}
// 对fragment里的component执行$destroy
for (i = 0, l = this.children.length; i < l; i++) {
// Call destroy for all contained instances,
// with remove:false and defer:true.
// Defer is necessary because we need to
// keep the children to call detach hooks
// on them.
this.children[i].$destroy(false, true)
}
var dirs = this.unlink.dirs
for (i = 0, l = dirs.length; i < l; i++) {
// disable the watchers on all the directives
// so that the rendered content stays the same
// during removal.
dirs[i]._watcher && dirs[i]._watcher.teardown()
}
}
/**
* Destroy the fragment.
*/
Fragment.prototype.destroy = function () {
if (this.parentFrag) {
this.parentFrag.childFrags.$remove(this)
}
this.node.__v_frag = null
this.unlink()
}
/**
* Call attach hook for a Vue instance.
*
* @param {Vue} child
*/
function attach (child) {
if (!child._isAttached && inDoc(child.$el)) {
child._callHook('attached')
}
}
/**
* Call detach hook for a Vue instance.
*
* @param {Vue} child
*/
function detach (child) {
if (child._isAttached && !inDoc(child.$el)) {
child._callHook('detached')
}
}