Vue源码解读-方法与事件绑定

version: 1.0.24
对源码的解析部分,纯属个人理解(不是纯属虚构),理解有误或者没有理解透彻的地方,欢迎多拍砖头
本文主要分析Vue源码中对方法与事件绑定的实现

前言

Vue中的事件有Dom事件Vue事件(自定义事件)两种,所以可将事件的绑定总结为一下几种类型:

  • 类型一:在模板中通过v-on指令绑定的Dom事件
  • 类型二:在模板中通过v-on指令绑定的自定义事件
  • 类型三:在vue options中通过events绑定的自定义事件
  • 类型四:通过$on方法绑定的自定义事件

下面就析上述几种类型事件的绑定做具体的分析

类型一:在模板中通过v-on指令绑定的Dom事件

采用此方式作为事件处理的原因及优点可参考Vue文档中为什么在 HTML 中监听事件?

模板中的v-on作为指令的一种,要通过模板编译(compile)和指令注册(directives)来实现, 其中涉及这两部分的内容将在其解读中再做分析。

先来看编译中的部分 compile/compile.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const onRE = /^v-on:|^@/
//编译元素中的指令
function compileDirectives (attrs, options) {
...
//关于“事件绑定”的处理
if (onRE.test(name)) {
arg = name.replace(onRE, '')
//添加一个“on”指令, publicDirectives.on为该指令的所有“钩子函数”等属性和方法
//“钩子函数”的介绍可参考Vue文档中自定义指令的介绍
pushDir('on', publicDirectives.on)
}
...
}

继续往下走,“on”指令中定义中的update方法进行了“事件的绑定”(其实只是调用了最后一步中的on方法), directives/public/on.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
update (handler) {
// "v-on"属性值为空的容错处理,
// 例如 @mousedown.prevent
if (!this.descriptor.raw) {
handler = function () {}
}
// "v-on"属性值不为空但不是函数,报错
if (typeof handler !== 'function') {
process.env.NODE_ENV !== 'production' && warn(
'v-on:' + this.arg + '="' +
this.expression + '" expects a function value, ' +
'got ' + handler,
this.vm
)
return
}
// 事件修饰符(modifiers)处理
if (this.modifiers.stop) {
// 返回一个新的方法,先执行event.stopPropagation(),再执行事件绑定事件
handler = stopFilter(handler)
}
if (this.modifiers.prevent) {
// 返回一个新的方法,先执行event.preventDefault(),再执行事件绑定事件
handler = preventFilter(handler)
}
if (this.modifiers.self) {
// 返回一个新的方法,如果触发元素为当前元素,才执行事件绑定事件
handler = selfFilter(handler)
}
// 事件按键修饰符处理
var keys = Object.keys(this.modifiers)
.filter(function (key) {
//排除事件修饰符
return key !== 'stop' &&
key !== 'prevent' &&
key !== 'self' &&
key !== 'capture'
})
if (keys.length) {
// 返回一个新的方法,如果触发元素的keyCode等于按键修饰符中的code,才执行事件绑定事件
handler = keyFilter(handler, keys)
}
// 避免重复绑定,先解绑
this.reset()
this.handler = handler
if (this.iframeBind) {
//处理iframe中的事件绑定
this.iframeBind()
} else {
//调用on方法进行事件绑定
on(
this.el,
this.arg,
this.handler,
this.modifiers.capture
)
}
}

最后一步进行“真正的事件绑定”, util/dom.js

1
2
3
export function on (el, event, cb, useCapture) {
el.addEventListener(event, cb, useCapture)
}

类型二:在模板中通过v-on指令绑定的自定义事件

类型三:在vue options中通过events绑定的自定义事件

类型四:通过$on方法绑定的自定义事件

这三种类型都是关于自定义事件的绑定,放在一起看,instance/internal/events.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
const eventRE = /^v-on:|^@/
//vue的初始化方法_init中会调用该方法来初始化事件绑定
Vue.prototype._initEvents = function () {
var options = this.$options
//对于类型二的事件绑定,如<child v-on:child-msg="handleIt"></child>
if (options._asComponent) {
registerComponentEvents(this, options.el)
}
//对于类型三的事件绑定
registerCallbacks(this, '$on', options.events)
//绑定watch事件
registerCallbacks(this, '$watch', options.watch)
}
//从上面的判断可以看出,这种类型只针对于子组件
//作用是在子组件中触发事件,父组件中的方法执行
function registerComponentEvents (vm, el) {
var attrs = el.attributes
var name, value, handler
for (var i = 0, l = attrs.length; i < l; i++) {
name = attrs[i].name
if (eventRE.test(name)) {
name = name.replace(eventRE, '')
value = attrs[i].value
//此处用来区分“内联语句”,如果只是一个方法名需加上'apply', 使其可调用
if (isSimplePath(value)) {
value += '.apply(this, $arguments)'
}
//将表达式转换成一个function,具体可参考《Vue源码解读-get-set的内部实现》
handler = (vm._scope || vm._context).$eval(value, true)
//标记方法属于父组件
handler._fromParent = true
//调用$on, 同类型四
vm.$on(name.replace(eventRE), handler)
}
}
}
//遍历options.events,逐一进行绑定
function registerCallbacks (vm, action, hash) {
if (!hash) return
var handlers, key, i, j
for (key in hash) {
handlers = hash[key]
if (isArray(handlers)) {
for (i = 0, j = handlers.length; i < j; i++) {
register(vm, action, key, handlers[i])
}
} else {
register(vm, action, key, handlers)
}
}
}
function register (vm, action, key, handler, options) {
var type = typeof handler
if (type === 'function') {
//如果handler是函数,直接绑定
vm[action](key, handler, options)
} else if (type === 'string') {
//如果handler是字符串,从"options.methods"中找出该方法进行绑定,如果没有就报错
var methods = vm.$options.methods
var method = methods && methods[handler]
if (method) {
vm[action](key, method, options)
} else {
process.env.NODE_ENV !== 'production' && warn(
'Unknown method: "' + handler + '" when ' +
'registering callback for ' + action +
': "' + key + '".',
vm
)
}
} else if (handler && type === 'object') {
//watch事件
register(vm, action, key, handler.handler, handler)
}
}