vue-event

编译和生成代码

  • parse的时候添加属性, generate的时候处理属性

编译

  • 在编译标签时handleStartTag->processElement->processAttrs会去解析事件绑定属性
  • addHandler在ast上添加事件属性nativeEvents/events, events[name] = newHandler
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
function parseModifiers (name) { // .x.y => {x: true, y: true}
var match = name.match(modifierRE);
if (match) {
var ret = {};
match.forEach(function (m) { ret[m.slice(1)] = true; });
return ret
}
}
function processAttrs (el) {
var list = el.attrsList;
var i, l, name, rawName, value, modifiers, isProp;
for (i = 0, l = list.length; i < l; i++) { // 遍历属性
name = rawName = list[i].name;
value = list[i].value;
if (dirRE.test(name)) { // /^v-|^@|^:/
// mark element as dynamic
el.hasBindings = true;
// modifiers
modifiers = parseModifiers(name);
if (modifiers) {
name = name.replace(modifierRE, '');
}
if (bindRE.test(name)) {
// v-bind
} else if (onRE.test(name)) { // v-on
name = name.replace(onRE, '');
addHandler(el, name, value, modifiers, false, warn$2);
} else {
// normal directives
}
} else {
// literal attribute
addAttr(el, name, JSON.stringify(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
function addHandler (el, name, value, modifiers, important, warn) {
// check capture/once/passive modifier
if (modifiers.xx) {
delete modifiers.xx;
name = '!' + name; // mark the event
}
// normalize click.right and click.middle
var events;
if (modifiers.native) {
delete modifiers.native;
events = el.nativeEvents || (el.nativeEvents = {});
} else {
events = el.events || (el.events = {});
}
var newHandler = {
value: value.trim()
};
newHandler.modifiers = modifiers;
if (Array.isArray(handlers)) { // 添加两次以上
important ? handlers.unshift(newHandler) : handlers.push(newHandler);
} else if (handlers) { // 第二次添加
events[name] = important ? [newHandler, handlers] : [handlers, newHandler];
} else {
events[name] = newHandler; // 第一次添加
}
el.plain = false;
}
// el.nativeEvents = {
// click: {
// value: 'clickHandler',
// modifiers: {
// prevent: true
// }
// }
// }

生成代码

  • generate.genElement.genData.genHandlers
    • 对Handler判断是表达式还是方法名, 方法名直接返回, 表达式外面套个functionfunction($event){(handler.value)}, 并传入$event
    • 有modifier通过modifierCode添加对应的代码后和上一步一起返回.
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
function genHandlers ( // 遍历事件 拼接成字符串 nativeOn:{ xx: xxx, yy: yyy }
events,
isNative,
warn
) {
var res = isNative ? 'nativeOn:{' : 'on:{';
for (var name in events) {
res += "\"" + name + "\":" + (genHandler(name, events[name])) + ",";
}
return res.slice(0, -1) + '}'
}
var modifierCode = {
stop: '$event.stopPropagation();',
prevent: '$event.preventDefault();',
self: genGuard("$event.target !== $event.currentTarget"),
ctrl: genGuard("!$event.ctrlKey"),
shift: genGuard("!$event.shiftKey"),
alt: genGuard("!$event.altKey"),
meta: genGuard("!$event.metaKey"),
left: genGuard("'button' in $event && $event.button !== 0"),
middle: genGuard("'button' in $event && $event.button !== 1"),
right: genGuard("'button' in $event && $event.button !== 2")
};
function genHandler (
name, // select
handler // {value: "selectHandler"}
) {
if (!handler) {
return 'function(){}'
}
if (Array.isArray(handler)) { // 处理多个事件回调
return ("[" + (handler.map(function (handler) { return genHandler(name, handler); }).join(',')) + "]")
}
var isMethodPath = simplePathRE.test(handler.value); // x.y x[1] x[a] x['a'] x["a"]
var isFunctionExpression = fnExpRE.test(handler.value); / () => or xx => or function(
if (!handler.modifiers) { // 没有修饰器就返回
if (isMethodPath || isFunctionExpression) {
return handler.value
}
/* istanbul ignore if */
return ("function($event){" + (handler.value) + "}") // inline statement
} else { // 处理modifiers
var code = '';
var genModifierCode = '';
var keys = [];
for (var key in handler.modifiers) {
if (modifierCode[key]) {
genModifierCode += modifierCode[key];
// left/right
if (keyCodes[key]) {
keys.push(key);
}
} else if (key === 'exact') {
var modifiers = (handler.modifiers);
genModifierCode += genGuard(
['ctrl', 'shift', 'alt', 'meta']
.filter(function (keyModifier) { return !modifiers[keyModifier]; })
.map(function (keyModifier) { return ("$event." + keyModifier + "Key"); })
.join('||')
);
} else {
keys.push(key);
}
}
if (keys.length) {
code += genKeyFilter(keys);
}
// Make sure modifiers like prevent and stop get executed after key filtering
if (genModifierCode) {
code += genModifierCode;
}
var handlerCode = isMethodPath
? ("return " + (handler.value) + "($event)")
: isFunctionExpression
? ("return (" + (handler.value) + ")($event)") // 返回函数内部调用方法并传入$event
: handler.value;
/* istanbul ignore if */
return ("function($event){" + code + handlerCode + "}") // function call expression
}
}
// {
// on: {"select": selectHandler},
// nativeOn: {"click": function($event) {
// $event.preventDefault();
// return clickHandler($event)
// }
// }
// }
// {
// on: {"click": function($event) {
// clickHandler($event)
// }
// }
// }

dom事件

  • create / update hook事件绑定更新
  • event hooks 安装和调用
    • modules在src/platforms/web/runtime/modules.
    • events的create和update都是调用updateDOMListeners, create时oldVnode是空
    • invokeCreateHooks调用create, patchVnode里调用update hook
      • invokeCreateHooks在createElm, initComponent中调用,vue实例化和初始化组件时
        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
        var events = {
        create: updateDOMListeners,
        update: updateDOMListeners
        }
        var platformModules = [
        attrs, klass, events, domProps, style, transition
        ]
        var modules = platformModules.concat(baseModules);
        var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules });
        function createPatchFunction (backend) { // hooks在创建patch方法混入
        var i, j;
        var cbs = {}; // 平台module和basemodule都保存在cbs, 组件update的时候调用
        var modules = backend.modules;
        for (i = 0; i < hooks.length; ++i) {
        cbs[hooks[i]] = [];
        for (j = 0; j < modules.length; ++j) {
        if (isDef(modules[j][hooks[i]])) {
        cbs[hooks[i]].push(modules[j][hooks[i]]);
        }
        }
        }
        }
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
function createElm (
vnode, insertedVnodeQueue, parentElm,)
{
createChildren(vnode, children, insertedVnodeQueue);
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue); // new vue时调用
}
insert(parentElm, vnode.elm, refElm);
}
function initComponent (vnode, insertedVnodeQueue) {
if (isDef(vnode.data.pendingInsert)) {
insertedVnodeQueue.push.apply(insertedVnodeQueue, vnode.data.pendingInsert);
vnode.data.pendingInsert = null;
}
vnode.elm = vnode.componentInstance.$el;
if (isPatchable(vnode)) {
invokeCreateHooks(vnode, insertedVnodeQueue); // 创建组件时调用
setScope(vnode);
} else {
// empty component root.
// skip all element-related modules except for ref (#3455)
registerRef(vnode);
// make sure to invoke the insert hook
insertedVnodeQueue.push(vnode);
}
}
  • 调用hook
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
function invokeCreateHooks (vnode, insertedVnodeQueue) {
for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) {
cbs.create[i$1](emptyNode, vnode);
}
i = vnode.data.hook; // Reuse variable
if (isDef(i)) {
if (isDef(i.create)) { i.create(emptyNode, vnode); } // create
if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
}
}
function patchVnode (oldVnode, vnode, insertedVnodeQueue, removeOnly) {
var i;
var data = vnode.data;
if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { // data上vnode的hook怎么挂的 ? prepatch
i(oldVnode, vnode);
}
var oldCh = oldVnode.children;
var ch = vnode.children;
if (isDef(data) && isPatchable(vnode)) {
for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); } // update
if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); }
}
}

dom事件绑定

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
function updateDOMListeners (oldVnode, vnode) {
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return
}
var on = vnode.data.on || {};
var oldOn = oldVnode.data.on || {};
target$1 = vnode.elm;
normalizeEvents(on); // normalize v-model event
updateListeners(on, oldOn, add$1, remove$2, vnode.context);
target$1 = undefined;
}
// 遍历data.on, 更新事件监听
function updateListeners (
on,
oldOn,
add,
remove$$1,
vm
) {
var name, def, cur, old, event;
for (name in on) {
def = cur = on[name];
old = oldOn[name];
event = normalizeEvent(name); // once, capture转成事件绑定的option
if (isUndef(old)) {
if (isUndef(cur.fns)) {
cur = on[name] = createFnInvoker(cur); // 修改回调函数, update不需要重新绑定
}
add(event.name, cur, event.once, event.capture, event.passive, event.params); // add new
} else if (cur !== old) {
old.fns = cur;
on[name] = old;
}
}
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name);
remove$$1(event.name, oldOn[name], event.capture); // remove unused
}
}
}
function createFnInvoker (fns) {
function invoker () {
var arguments$1 = arguments;
var fns = invoker.fns;
if (Array.isArray(fns)) {
var cloned = fns.slice();
for (var i = 0; i < cloned.length; i++) {
cloned[i].apply(null, arguments$1);
}
} else {
// return handler return value for single handlers
return fns.apply(null, arguments)
}
}
invoker.fns = fns;
return invoker
}
function add$1 (event, handler, once$$1, capture, passive) {
handler = withMacroTask(handler); // 事件回调引起的state change nextTick通过macro task执行回调
if (once$$1) { handler = createOnceHandler(handler, event, capture); }
target$1.addEventListener(
event,
handler,
supportsPassive
? { capture: capture, passive: passive }
: capture
);
}
function remove$2 (
event,
handler,
capture,
_target
) {
(_target || target$1).removeEventListener(
event,
handler._withTask || handler,
capture
);
}
/**
* Wrap a function so that if any code inside triggers state change,
* the changes are queued using a (macro) task instead of a microtask.
*/
function withMacroTask (fn) {
return fn._withTask || (fn._withTask = function () {
useMacroTask = true;
var res = fn.apply(null, arguments);
useMacroTask = false;
return res
})
}

自定义事件

  • 父组件render的时候发现child是组件, 有非native listener就作为componentOptions传入new Vnode(组件占位符节点)
    • 子组件初始化时调用initInternalComponent initEvents, 把vm.$options._parentListeners里的listener传给updateListeners做dom事件类似的更新监听.
    • componentListener的add和remove方法调用vm.$on $off方法
    • vm._events上有各类型的事件监听, 通过发布订阅模式实现事件回调
    • 子组件update的时候监听native listener.
  • 父组件update的时候监听native listener.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function createComponent (Ctor, data, context, children, tag) {
// ...
// extract listeners, since these needs to be treated as
// child component listeners instead of DOM listeners
var listeners = data.on;
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn;
// install component management hooks onto the placeholder node
installComponentHooks(data);
var vnode = new VNode(
("vue-component-" + (Ctor.cid) + (name ? ("-" + name) : '')),
data, undefined, undefined, undefined, context,
{ Ctor: Ctor, propsData: propsData, listeners: listeners, tag: tag, children: children },
asyncFactory
);
}
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
// patch子组件
function createComponentInstanceForVnode (vnode, parent, parentElm, refElm) {
var options = {
_isComponent: true,
parent: parent,
_parentVnode: vnode, // 占位符节点里有`listeners: listeners`
_parentElm: parentElm || null,
_refElm: refElm || null
};
return new vnode.componentOptions.Ctor(options)
}
Vue.prototype._init = function (options) {
var vm = this;
if (options && options._isComponent) {
initInternalComponent(vm, options);
}
// init xxx
initEvents(vm);
if (vm.$options.el) {
vm.$mount(vm.$options.el);
}
}
// options就是上面createComponentInstanceForVnode的options
function initInternalComponent (vm, options) {
vm.$options = Object.create(vm.constructor.options);
// ....
vm.$options._parentVnode = options._parentVnode;
var parentVnode = options._parentVnode;
var vnodeComponentOptions = parentVnode.componentOptions;
opts._parentListeners = vnodeComponentOptions.listeners;
}
function initEvents (vm) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
// init parent attached events
var listeners = vm.$options._parentListeners;// {select: invoker().fns}
if (listeners) {
updateComponentListeners(vm, listeners);
}
}
1
2
3
4
5
6
7
8
9
function updateComponentListeners (
vm,
listeners,
oldListeners
) {
target = vm;
updateListeners(listeners, oldListeners || {}, add, remove$1, vm);
target = undefined;
}
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
function eventsMixin (Vue) {
var hookRE = /^hook:/;
Vue.prototype.$on = function (event, fn) {
var this$1 = this;
var vm = this;
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
this$1.$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
if (hookRE.test(event)) {
vm._hasHookEvent = true;
}
}
return vm
};
Vue.prototype.$once = function (event, fn) {
var vm = 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, fn) {
var this$1 = this;
var vm = this;
// all
if (!arguments.length) {
vm._events = Object.create(null);
return vm
}
// array of events
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
this$1.$off(event[i], fn);
}
return vm
}
// specific event
var cbs = vm._events[event];
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null;
return vm
}
if (fn) {
// specific handler
var cb;
var i$1 = cbs.length;
while (i$1--) {
cb = cbs[i$1];
if (cb === fn || cb.fn === fn) {
cbs.splice(i$1, 1);
break
}
}
}
return vm
};
Vue.prototype.$emit = function (event) {
var vm = this;
var cbs = vm._events[event]; // cbs是父组件定义但, 子组件实例化的时候作为选项传入了
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
var args = toArray(arguments, 1);
for (var i = 0, l = cbs.length; i < l; i++) {
try {
cbs[i].apply(vm, args);
} catch (e) {
handleError(e, vm, ("event handler for \"" + event + "\""));
}
}
}
return vm
};
}