Vue源码详解之link函数
# Vue源码详解之link函数
compile结束后就到了link阶段。前文说了所有的link函数都是被linkAndCapture包裹着执行的。那就先看看linkAndCapture:
// link函数的执行过程会生成新的Directive实例,push到_directives数组中
// 而这些_directives并没有建立对应的watcher,watcher也没有收集依赖,
// 一切都还处于初始阶段,因此capture阶段需要找到这些新添加的directive,
// 依次执行_bind,在_bind里会进行watcher生成,执行指令的bind和update,完成响应式构建
function linkAndCapture (linker, vm) {
// 先记录下数组里原先有多少元素,他们都是已经执行过_bind的,我们只_bind新添加的directive
var originalDirCount = vm._directives.length
linker()
// slice出新添加的指令们
var dirs = vm._directives.slice(originalDirCount)
// 对指令进行优先级排序,使得后面指令的bind过程是按优先级从高到低进行的
dirs.sort(directiveComparator)
for (var i = 0, l = dirs.length; i < l; i++) {
dirs[i]._bind()
}
return dirs
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// makeNodeLinkFn就是compileDirective最后执行并且return出去返回值的函数
// 它让link函数闭包住编译阶段生成好的指令描述对象(他们还不是Directive实例,虽然变量名叫做directives)
function makeNodeLinkFn (directives) {
return function nodeLinkFn (vm, el, host, scope, frag) {
// reverse apply because it's sorted low to high
var i = directives.length
while (i--) {
vm._bindDir(directives[i], el, host, scope, frag)
}
}
}
// 这就是vm._bindDir
Vue.prototype._bindDir = function (descriptor, node, host, scope, frag) {
this._directives.push(
new Directive(descriptor, this, node, host, scope, frag)
)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Directive.prototype._bind = function () {
var name = this.name
var descriptor = this.descriptor
// remove attribute
if (
// 只要不是cloak指令那就从dom的attribute里移除
// 是cloak指令但是已经编译和link完成了的话,那也还是可以移除的
(name !== 'cloak' || this.vm._isCompiled) &&
this.el && this.el.removeAttribute
) {
var attr = descriptor.attr || ('v-' + name)
this.el.removeAttribute(attr)
}
// copy def properties
// 不采用原型链继承,而是直接extend定义对象到this上,来扩展Directive实例
var def = descriptor.def
if (typeof def === 'function') {
this.update = def
} else {
extend(this, def)
}
// setup directive params
// 获取指令的参数, 对于一些指令, 指令的元素上可能存在其他的attr来作为指令运行的参数
// 比如v-for指令,那么元素上的attr: track-by="..." 就是参数
// 比如组件指令,那么元素上可能写了transition-mode="out-in", 诸如此类
this._setupParams()
// initial bind
if (this.bind) {
this.bind()
}
this._bound = true
if (this.literal) {
this.update && this.update(descriptor.raw)
} else if (
// 下面这些判断是因为许多指令比如slot component之类的并不是响应式的,
// 他们只需要在bind里处理好dom的分发和编译/link即可然后他们的使命就结束了,生成watcher和收集依赖等步骤根本没有
// 所以根本不用执行下面的处理
(this.expression || this.modifiers) &&
(this.update || this.twoWay) &&
!this._checkStatement()
) {
// wrapped updater for context
var dir = this
if (this.update) {
// 处理一下原本的update函数,加入lock判断
this._update = function (val, oldVal) {
if (!dir._locked) {
dir.update(val, oldVal)
}
}
} else {
this._update = noop
}
// 绑定好 预处理 和 后处理 函数的this,因为他们即将作为属性放入一个参数对象当中,不绑定的话this会变
var preProcess = this._preProcess
? bind(this._preProcess, this)
: null
var postProcess = this._postProcess
? bind(this._postProcess, this)
: null
var watcher = this._watcher = new Watcher(
this.vm,
this.expression,
this._update, // callback
{
filters: this.filters,
twoWay: this.twoWay,//twoWay指令和deep指令请参见官网自定义指令章节
deep: this.deep, //twoWay指令和deep指令请参见官网自定义指令章节
preProcess: preProcess,
postProcess: postProcess,
scope: this._scope
}
)
// v-model with inital inline value need to sync back to
// model instead of update to DOM on init. They would
// set the afterBind hook to indicate that.
if (this.afterBind) {
this.afterBind()
} else if (this.update) {
this.update(watcher.value)
}
}
}
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
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
// v-bind指令的指令定义对象 [有删节]
export default {
...
bind () {
var attr = this.arg
var tag = this.el.tagName
// handle interpolation bindings
const descriptor = this.descriptor
const tokens = descriptor.interp
if (tokens) {
// handle interpolations with one-time tokens
if (descriptor.hasOneTime) {
// 对于单次插值的情况
// 在tokensToExp内部使用$eval将表达式'a '+val+' c'转换为'"a " + "text" + " c"',以此结果为新表达式
// $eval过程中未设置Dep.target,因而不会订阅任何依赖,
// 而后续Watcher.get在计算这个新的纯字符串表达式过程中虽然设置了target但必然不会触发任何getter,也不会订阅任何依赖
// 单次插值由此完成
this.expression = tokensToExp(tokens, this._scope || this.vm)
}
}
},
....
}
// v-text指令的执行定义对象
export default {
bind () {
this.attr = this.el.nodeType === 3
? 'data'
: 'textContent'
},
update (value) {
this.el[this.attr] = _toString(value)
}
}
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
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
export default function Watcher (vm, expOrFn, cb, options) {
// mix in options
if (options) {
extend(this, options)
}
var isFn = typeof expOrFn === 'function'
this.vm = vm
vm._watchers.push(this)
this.expression = expOrFn
// 把回调放在this上, 在完成了一轮的数据变动之后,在批处理最后阶段执行cb, cb一般是dom操作
this.cb = cb
this.id = ++uid // uid for batching
this.active = true
// lazy watcher主要应用在计算属性里,我在注释版源码里进行了解释,这里大家先跳过
this.dirty = this.lazy // for lazy watchers
// 用deps存储当前的依赖,而新一轮的依赖收集过程中收集到的依赖则会放到newDeps中
// 之所以要用一个新的数组存放新的依赖是因为当依赖变动之后,
// 比如由依赖a和b变成依赖a和c
// 那么需要把原先的依赖订阅清除掉,也就是从b的subs数组中移除当前watcher,因为我已经不想监听b的变动
// 所以我需要比对deps和newDeps,找出那些不再依赖的dep,然后dep.removeSub(当前watcher),这一步在afterGet中完成
this.deps = []
this.newDeps = []
// 这两个set是用来提升比对过程的效率,不用set的话,判断deps中的一个dep是否在newDeps中的复杂度是O(n)
// 改用set来判断的话,就是O(1)
this.depIds = new Set()
this.newDepIds = new Set()
this.prevError = null // for async error stacks
// parse expression for getter/setter
if (isFn) {
// 对于计算属性而言就会进入这里,我们先忽略
this.getter = expOrFn
this.setter = undefined
} else {
// 把expression解析为一个对象,对象的get/set属性存放了获取/设置的函数
// 比如hello解析的get函数为function(scope) {return scope.hello;}
var res = parseExpression(expOrFn, this.twoWay)
this.getter = res.get
// 比如scope.a = {b: {c: 0}} 而expression为a.b.c
// 执行res.set(scope, 123)能使scope.a变成{b: {c: 123}}
this.setter = res.set
}
// 执行get(),既拿到表达式的值,又完成第一轮的依赖收集,使得watcher订阅到相关的依赖
// 如果是lazy则不在此处计算初值
this.value = this.lazy
? undefined
: this.get()
// state for avoiding false triggers for deep and Array
// watchers during vm._digest()
this.queued = this.shallow = false
}
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
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
export function parseExpression (exp, needSet) {
exp = exp.trim()
// try cache
// 缓存机制
var hit = expressionCache.get(exp)
if (hit) {
if (needSet && !hit.set) {
hit.set = compileSetter(hit.exp)
}
return hit
}
var res = { exp: exp }
res.get = isSimplePath(exp) && exp.indexOf('[') < 0
// optimized super simple getter
? makeGetterFn('scope.' + exp)
// dynamic getter
// 如果不是简单Path, 也就是语句了,那么就要对这个字符串做一些额外的处理了,
// 主要是在变量前加上'scope.'
: compileGetter(exp)
if (needSet) {
res.set = compileSetter(exp)
}
expressionCache.put(exp, res)
return res
}
const pathTestRE = // pathTestRE太长了,其就是就是检测是否是a或者a['xxx']或者a.xx.xx.xx这种表达式
const literalValueRE = /^(?:true|false|null|undefined|Infinity|NaN)$/
function isSimplePath (exp) {
// 检查是否是 a['b'] 或者 a.b.c 这样的
// 或者是true false null 这种字面量
// 再或者就是Math.max这样,
// 对于a=true和a/=2和hello()这种就不是simple path
return pathTestRE.test(exp) &&
// don't treat literal values as paths
!literalValueRE.test(exp) &&
// Math constants e.g. Math.PI, Math.E etc.
exp.slice(0, 5) !== 'Math.'
}
function makeGetterFn (body) {
return new Function('scope', 'return ' + body + ';')
}
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
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
Watcher.prototype.beforeGet = function () {
Dep.target = this
}
Watcher.prototype.get = function () {
this.beforeGet()
// v-for情况下,this.scope有值,是对应的数组元素,其继承自this.vm
var scope = this.scope || this.vm
var value
try {
// 执行getter,这一步很精妙,表面上看是求出指令的初始值,
// 其实也完成了初始的依赖收集操作,即:让当前的Watcher订阅到对应的依赖(Dep)
// 比如a+b这样的expression实际是依赖两个a和b变量,this.getter的求值过程中
// 会依次触发a 和 b的getter,在observer/index.js:defineReactive函数中,我们定义好了他们的getter
// 他们的getter会将Dep.target也就是当前Watcher加入到自己的subs(订阅者数组)里
value = this.getter.call(scope, scope)
} catch (e) {
// 输出相关warn信息
}
// "touch" every property so they are all tracked as
// dependencies for deep watching
// deep指令的处理,类似于我在文章开头写的那个遍历所有属性的touch函数,大家请跳过此处
if (this.deep) {
traverse(value)
}
if (this.preProcess) {
value = this.preProcess(value)
}
if (this.filters) {
// 若有过滤器则对value执行过滤器,请跳过
value = scope._applyFilters(value, null, this.filters, false)
}
if (this.postProcess) {
value = this.postProcess(value)
}
this.afterGet()
return value
}
// 新一轮的依赖收集后,依赖被收集到this.newDepIds和this.newDeps里
// this.deps存储的上一轮的的依赖此时将会被遍历, 找出其中不再依赖的dep,将自己从dep的subs列表中清除
// 不再订阅那些不依赖的dep
Watcher.prototype.afterGet = function () {
Dep.target = null
var i = this.deps.length
while (i--) {
var dep = this.deps[i]
if (!this.newDepIds.has(dep.id)) {
dep.removeSub(this)
}
}
// 清除订阅完成,this.depIds和this.newDepIds交换后清空this.newDepIds
var tmp = this.depIds
this.depIds = this.newDepIds
this.newDepIds = tmp
this.newDepIds.clear()
// 同上,清空数组
tmp = this.deps
this.deps = this.newDeps
this.newDeps = tmp
this.newDeps.length = 0
}
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