投身烈火
Keep Walking

vue源码阅读笔记(8)

上次看了生命周期部分的代码,这次准备看 event 的代码

Vue构造函数和vue实例 instance

events.js

之前说过,实例这部分的文件里,一般包括两部分,一部分用在实例init的过程,一部分用在定义Vue构造函数的mixin。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
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
/* @flow */
import { toArray } from '../util/index'
import { updateListeners } from '../vdom/helpers/index'
export function initEvents (vm: Component) {
// 这个属性是用来记录当前实例注册过的事件的
vm._events = Object.create(null)
// hook event应该是系统事件
vm._hasHookEvent = false
// init parent attached events
// _parentListeners其实是父组件模板中写的v-on
// 所以下面这段就是将父组件模板中注册的事件放到当前组件实例的listeners里面
const listeners = vm.$options._parentListeners
if (listeners) {
updateComponentListeners(vm, listeners)
}
}
// 用来存当前vm实例的临时变量
let target: Component
// 添加删除事件的方法,专门给updateComponentListeners用的
// 特意定义在外面可能是为了省内存吧?要是我就直接写匿名方法了……
function add (event, fn, once) {
if (once) {
target.$once(event, fn)
} else {
target.$on(event, fn)
}
}
function remove (event, fn) {
target.$off(event, fn)
}
// 这个方法感觉是个过渡方法,用来让add和remove能够调用到vm实例用的
// 真正绑定事件updateListeners,
// 顺便一提的是,listeners里面的方法都已经用bind包装过了,所以不存在指向对象不对的情况
// 具体包装的时机我找了半天死活没找到,现在可以确定的是,在render函数生成的时候,就已经包装好了
export function updateComponentListeners (
vm: Component,
listeners: Object,
oldListeners: ?Object
) {
target = vm
updateListeners(listeners, oldListeners || {}, add, remove, vm)
}
// 往构造函数原型链上挂载用来注册的方法
export function eventsMixin (Vue: Class<Component>) {
const hookRE = /^hook:/
// 常规的事件回调队列,每个事件对一个数组
Vue.prototype.$on = function (event: string | Array<string>, fn: Function): Component {
const vm: Component = this
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$on(event[i], fn)
}
} else {
(vm._events[event] || (vm._events[event] = [])).push(fn)
// optimize hook:event cost by using a boolean flag marked at registration
// instead of a hash lookup
// 这里标记为true之后每次update的时候就会用emit发出hook的消息
if (hookRE.test(event)) {
vm._hasHookEvent = true
}
}
return vm
}
// 在on上包装了一层,执行一次就off了
Vue.prototype.$once = function (event: string, fn: Function): Component {
const vm: Component = this
function on () {
vm.$off(event, on)
fn.apply(vm, arguments)
}
on.fn = fn
vm.$on(event, on)
return vm
}
// 删除事件对应回调用的
Vue.prototype.$off = function (event?: string, fn?: Function): Component {
const vm: Component = this
// all
// 不传参数就全删了
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// specific event
// 删除指定事件的回调队列
const cbs = vm._events[event]
if (!cbs) {
return vm
}
if (arguments.length === 1) {
vm._events[event] = null
return vm
}
// specific handler
// 删除指定的回调
let cb
let i = cbs.length
while (i--) {
cb = cbs[i]
// 之前说过回调队列中的函数(就是cb)都用bind包装过了
// 目测cb.fn就是原始的函数,所以用来比较了
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1)
break
}
}
return vm
}
// 触发对应事件的函数队列
Vue.prototype.$emit = function (event: string): Component {
const vm: Component = this
let cbs = vm._events[event]
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs
// 去掉参数列表的第一个,其他的都作为cb的参数,转成数组
const args = toArray(arguments, 1)
for (let i = 0, l = cbs.length; i < l; i++) {
cbs[i].apply(vm, args)
}
}
return vm
}
}

好的那么由于时间不足,本期的博客就写到这里了,为啥这次更新的内容这么少?因为光一个updateListeners我就看了一上午……(T_T)……如果不出意外的话,maybe可能也许大概下周五会更新吧,能不能准时更新,就全看米娜桑点赞转发安利留言的力度了~!

白了个白~!