К этому моменту в этой серии были проанализированы основные основные вещи Vue, но сила Vue неотделима от практических функций, которые он предоставляет пользователям.Разработчики могут предпочесть реализацию бизнес-логики, а не базовые функции. Например, в повседневной разработке мы бы
@click=***Это очень полезно, но думаем ли мы о том, как Vue выполняет обработку событий для наших шаблонов в задней части, и мы часто используем пользовательские события компонентов для реализации связи между родителем и дочерним элементом, тогда это событие и событие родного dom имеют Отличается ли он, каков принцип включения связи, при сомнениях углубляемся в исходный код для анализа.
9.1. Составление шаблона
VueПеред монтированием инстанса много работы по компиляции шаблона,templateШаблон компилируется и анализируется вASTдерево, превращенное вrenderфункцию, а сrenderТолько после этого функция войдет в процесс монтирования экземпляра. Для мероприятий мы часто используемv-onили@Привязать события к шаблону. Следовательно, первым шагом в работе с событием является сбор и обработка инструкций события на этапе компиляции.
Информация, собранная на этапе компиляции простого анализа использования:
<div id="app">
<div v-on:click.stop="doThis">点击</div>
<span>{{count}}</span>
</div>
<script>
var vm = new Vue({
el: '#app',
data() {
return {
count: 1
}
},
methods: {
doThis() {
++this.count
}
}
})
</script>
Мы предварительно скомпилировали шаблон, и вход в скомпилированный шаблон находитсяvar ast = parse(template.trim(), options);середина,parseРазбивая строку шаблона, анализируя ее вASTдерево, где для обработки атрибутов, вprocessAttrВ связи с большим количеством ответвлений мы только разбираем процесс на примере.
var dirRE = /^v-|^@|^:/;
function processAttrs (el) {
var list = el.attrsList;
var i, l, name, rawName, value, modifiers, syncGen, isDynamic;
for (i = 0, l = list.length; i < l; i++) {
name = rawName = list[i].name; // v-on:click
value = list[i].value; // doThis
if (dirRE.test(name)) { // 匹配v-或者@开头的指令
el.hasBindings = true;
modifiers = parseModifiers(name.replace(dirRE, ''));// parseModifiers('on:click')
if (modifiers) {
name = name.replace(modifierRE, '');
}
if (bindRE.test(name)) { // v-bind分支
// ...留到v-bind指令时分析
} else if (onRE.test(name)) { // v-on分支
name = name.replace(onRE, ''); // 拿到真正的事件click
isDynamic = dynamicArgRE.test(name);// 动态事件绑定
if (isDynamic) {
name = name.slice(1, -1);
}
addHandler(el, name, value, modifiers, false, warn$2, list[i], isDynamic);
} else { // normal directives
// 其他指令相关逻辑
} else {}
}
}
processAttrsХотя логики больше, она относительно проста для понимания.var dirRE = /^v-|^@|^:/;Это регулярное событие, связанное с совпадающим событием, и дефект, который соответствует совпадению, получит соответствующий контент инструкции события, включая само событие, обратный вызов события и модификатор события. наконец прошлоaddHandlerметод, дляASTдерево для добавления свойств, связанных с событием. а такжеaddHandlerДругой важной особенностью является особая обработка модификаторов событий.
// el是当前解析的AST树
function addHandler (el,name,value,modifiers,important,warn,range,dynamic) {
modifiers = modifiers || emptyObject;
// passive 和 prevent不能同时使用,可以参照官方文档说明
if (
warn &&
modifiers.prevent && modifiers.passive
) {
warn(
'passive and prevent can\'t be used together. ' +
'Passive handler can\'t prevent default event.',
range
);
}
// 这部分的逻辑会对特殊的修饰符做字符串拼接的处理,以备后续的使用
if (modifiers.right) {
if (dynamic) {
name = "(" + name + ")==='click'?'contextmenu':(" + name + ")";
} else if (name === 'click') {
name = 'contextmenu';
delete modifiers.right;
}
} else if (modifiers.middle) {
if (dynamic) {
name = "(" + name + ")==='click'?'mouseup':(" + name + ")";
} else if (name === 'click') {
name = 'mouseup';
}
}
if (modifiers.capture) {
delete modifiers.capture;
name = prependModifierMarker('!', name, dynamic);
}
if (modifiers.once) {
delete modifiers.once;
name = prependModifierMarker('~', name, dynamic);
}
/* istanbul ignore if */
if (modifiers.passive) {
delete modifiers.passive;
name = prependModifierMarker('&', name, dynamic);
}
// events 用来记录绑定的事件
var events;
if (modifiers.native) {
delete modifiers.native;
events = el.nativeEvents || (el.nativeEvents = {});
} else {
events = el.events || (el.events = {});
}
var newHandler = rangeSetItem({ value: value.trim(), dynamic: dynamic }, range);
if (modifiers !== emptyObject) {
newHandler.modifiers = modifiers;
}
var handlers = events[name];
/* istanbul ignore if */
// 绑定的事件可以多个,回调也可以多个,最终会合并到数组中
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;
}
Обработка модификатора изменит результат склейки финальной строки, давайте посмотрим окончательное преобразованиеASTДерево:
9.2 Генерация кода
Последний шаг компиляции шаблона основан на парсингеASTДерево генерирует функцию рендеринга соответствующей платформы, т.е.renderПроцесс генерации функции, соответствующейvar code = generate(ast, options);.
function generate (ast,options) {
var state = new CodegenState(options);
var code = ast ? genElement(ast, state) : '_c("div")';
return {
render: ("with(this){return " + code + "}"), // with函数
staticRenderFns: state.staticRenderFns
}
}
Основная обработка находится вgetElementсередина,getElementФункция будет обрабатывать разные ветки по разным типам инструкций, и начнется компиляция обычных шаблоновgenDataОбработка в функции, тот же анализ только для обработки, связанной с событием, из предыдущего анализаASTДерево ясно видит,ASTбольше деревьевeventsхарактеристики,genHandlersфункция будетeventАтрибуты выполняют логическую обработку.
function genData (el, state) {
var data = '{';
// directives first.
// directives may mutate the el's other properties before they are generated.
var dirs = genDirectives(el, state);
if (dirs) { data += dirs + ','; }
//其他处理
···
// event handlers
if (el.events) {
data += (genHandlers(el.events, false)) + ",";
}
···
return data
}
genHandlersЛогика будет проходить через анализируемыйASTдерево, получитьeventСвойства объекта и объединяются в строки в соответствии с объектами событий в свойствах.
function genHandlers (events,isNative) {
var prefix = isNative ? 'nativeOn:' : 'on:';
var staticHandlers = "";
var dynamicHandlers = "";
// 遍历ast树解析好的event对象
for (var name in events) {
//genHandler本质上是将事件对象转换成可拼接的字符串
var handlerCode = genHandler(events[name]);
if (events[name] && events[name].dynamic) {
dynamicHandlers += name + "," + handlerCode + ",";
} else {
staticHandlers += "\"" + name + "\":" + handlerCode + ",";
}
}
staticHandlers = "{" + (staticHandlers.slice(0, -1)) + "}";
if (dynamicHandlers) {
return prefix + "_d(" + staticHandlers + ",[" + (dynamicHandlers.slice(0, -1)) + "])"
} else {
return prefix + staticHandlers
}
}
// 事件模板书写匹配
var isMethodPath = simplePathRE.test(handler.value); // doThis
var isFunctionExpression = fnExpRE.test(handler.value); // () => {} or function() {}
var isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, '')); // doThis($event)
function genHandler (handler) {
if (!handler) {
return 'function(){}'
}
// 事件绑定可以多个,多个在解析ast树时会以数组的形式存在,如果有多个则会递归调用getHandler方法返回数组。
if (Array.isArray(handler)) {
return ("[" + (handler.map(function (handler) { return genHandler(handler); }).join(',')) + "]")
}
// value: doThis 可以有三种方式
var isMethodPath = simplePathRE.test(handler.value); // doThis
var isFunctionExpression = fnExpRE.test(handler.value); // () => {} or function() {}
var isFunctionInvocation = simplePathRE.test(handler.value.replace(fnInvokeRE, '')); // doThis($event)
// 没有任何修饰符
if (!handler.modifiers) {
// 符合函数定义规范,则直接返回调用函数名 doThis
if (isMethodPath || isFunctionExpression) {
return handler.value
}
// 不符合则通过function函数封装返回
return ("function($event){" + (isFunctionInvocation ? ("return " + (handler.value)) : handler.value) + "}") // inline statement
} else {
// 包含修饰符的场景
}
}
В шаблоне есть три способа записи событий, которые соответствуют содержанию последнего регулярного совпадения в обращении.
-
<div @click="doThis"></div>
-
<div @click="doThis($event)"></div>
-
<div @click="()=>{}"></div> <div @click="function(){}"></div>
Для приведенного выше преобразования объекта события, если событие не имеет модификаторов и удовлетворяет правильному методу написания шаблона, имя вызывающего события будет возвращено напрямую.<div @click="console.log(11)"></div>Метод записи будет инкапсулирован вfunction($event){}середина.
Есть много сценариев, содержащих модификаторы, и мы приводим анализ отдельно. Как показано в приведенном выше примере,modifiers: { stop: true }получитеstopСоответствующий логическому сценарию, который необходимо добавить'$event.stopPropagation();'и добавьте его в возвращаемую строку функции.
function genHandler() {
// ···
} else {
var code = '';
var genModifierCode = '';
var keys = [];
// 遍历modifiers上记录的修饰符
for (var key in handler.modifiers) {
if (modifierCode[key]) {
// 根据修饰符添加对应js的代码
genModifierCode += modifierCode[key];
// left/right
if (keyCodes[key]) {
keys.push(key);
}
// 针对exact的处理
} 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)")
: isFunctionInvocation
? ("return " + (handler.value))
: handler.value;
return ("function($event){" + code + handlerCode + "}")
}
}
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")
};
После этого преобразования,withупакованныйrenderФункция выглядит следующим образом:
"_c('div',{attrs:{"id":"app"}},[_c('div',{on:{"click":function($event){$event.stopPropagation();return doThis($event)}}},[_v("点击")]),_v(" "),_c('span',[_v(_s(count))])])"
9.3. Привязка событий
Много места было потрачено на внедрение тега события на шаблоне в построении.ASTКак обрабатывается дерево и как оно строится в соответствии сASTдерево возвращает правильныйrenderфункция визуализации,Но настоящая привязка события по-прежнему неотделима от события регистрации привязки.. Этот этап происходит при монтировании компонента.
имеютrenderфункция, которая может естественным образом генерировать необходимое монтирование экземпляраVnodeдерево, и продолжитсяpatchVnodeПостроение реальных узлов осуществляется по ссылке предыдущей главы.Если процесс обнаружения был забыт, вы можете просмотреть предыдущие главы.VnodeНет очевидной разницы между процессом построения дерева и контентом, введенным ранее, поэтому этот процесс повторяться не будет, а окончательный сгенерированныйvnodeследующим образом:
имеютVnode, то он рекурсивно обходит дочерние узлы и вызываетcreateElmСоздать реальный для каждого дочернего узлаDOM,из-заVnodeимеютdataсвойств, при создании реальныхDOMБудет выполняться процесс регистрации связанных хуков, один из которых — регистрация обработки, связанной с событием.
function createElm() {
···
// 针对指令的处理
if (isDef(data)) {
invokeCreateHooks(vnode, insertedVnodeQueue);
}
}
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); }
if (isDef(i.insert)) { insertedVnodeQueue.push(vnode); }
}
}
var events = {
create: updateDOMListeners,
update: updateDOMListeners
};
мы часто бываем вtemplateопределено в шаблонеv-onмероприятие,v-bindдинамические свойства,v-textдинамические директивы и т. д., иv-onКак и директивы событий, все они компилируются на этапе компиляции иVnodeСоздание этапа сборкиdataсвойства, поэтомуinvokeCreateHooksЭто задача, обрабатываемая шаблонной инструкцией, которая создает разные задачи для реального этапа для разных инструкций. Для событий это вызоветupdateDOMListenersк реальномуDOMУзлы регистрируют событийные задачи.
function updateDOMListeners (oldVnode, vnode) {
// on是事件指令的标志
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return
}
// 新旧节点不同的事件绑定解绑
var on = vnode.data.on || {};
var oldOn = oldVnode.data.on || {};
// 拿到需要添加事件的真实DOM节点
target$1 = vnode.elm;
// normalizeEvents是对事件兼容性的处理
normalizeEvents(on);
updateListeners(on, oldOn, add$1, remove$2, createOnceHandler$1, vnode.context);
target$1 = undefined;
}
вnormalizeEventsнаправлен наv-modelобработка, такая как не поддерживается в IEchangeсобытие, используйте толькоinputсобытие вместо этого.
updateListenersЛогика тоже очень проста, он будет проходитьonСобытие связывает зарегистрированное событие с событием нового узла и удаляет прослушиватель событий со старого узла, который должен обрабатывать собственныйDOMДобавление и удаление событий, а также для обработки пользовательских событий добавления и удаления, пользовательских событий, последующего повторного анализа контента.
function updateListeners (on,oldOn,add,remove$$1,createOnceHandler,vm) {
var name, def$$1, cur, old, event;
// 遍历事件
for (name in on) {
def$$1 = cur = on[name];
old = oldOn[name];
event = normalizeEvent(name);
if (isUndef(cur)) {
// 事件名非法的报错处理
warn(
"Invalid handler for event \"" + (event.name) + "\": got " + String(cur),
vm
);
} else if (isUndef(old)) {
// 旧节点不存在
if (isUndef(cur.fns)) {
// createFunInvoker返回事件最终执行的回调函数
cur = on[name] = createFnInvoker(cur, vm);
}
// 只触发一次的事件
if (isTrue(event.once)) {
cur = on[name] = createOnceHandler(event.name, cur, event.capture);
}
// 执行真正注册事件的执行函数
add(event.name, cur, event.capture, event.passive, event.params);
} 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);
}
}
}
Когда экземпляр изначально создается, старый узел не существует, в это время он будет вызыватьсяcreateFnInvokerФункция инкапсулирует функцию обратного вызова события, поскольку для одного события может быть несколько обратных вызовов, поэтомуcreateFnInvokerФункция заключается в унифицированной инкапсуляции и обработке одиночных и множественных событий обратного вызова и возврате анонимной функции, которая фактически выполняется при срабатывании события.
function createFnInvoker (fns, vm) {
// 当事件触发时,执行invoker方法,方法执行fns
function invoker () {
var arguments$1 = arguments;
var fns = invoker.fns;
// fns是多个回调函数组成的数组
if (Array.isArray(fns)) {
var cloned = fns.slice();
for (var i = 0; i < cloned.length; i++) {
// 遍历执行真正的回调函数
invokeWithErrorHandling(cloned[i], null, arguments$1, vm, "v-on handler");
}
} else {
// return handler return value for single handlers
return invokeWithErrorHandling(fns, null, arguments, vm, "v-on handler")
}
}
invoker.fns = fns;
// 返回最终事件执行的回调函数
return invoker
}
вinvokeWithErrorHandlingБудет выполнена определенная функция обратного вызова, и здесь выполняется обработка ошибок синхронного асинхронного обратного вызова.try-catchИспользуется для синхронных обратных вызовов для перехвата ошибок исключений,Promise.catchИспользуется для перехвата ошибок, возвращаемых асинхронными задачами.
function invokeWithErrorHandling (handler,context,args,vm,info) {
var res;
try {
res = args ? handler.apply(context, args) : handler.call(context);
if (res && !res._isVue && isPromise(res)) {
// issue #9511
// reassign to res to avoid catch triggering multiple times when nested calls
// 当生命周期钩子函数内部执行返回promise对象是,如果捕获异常,则会对异常信息做一层包装返回
res = res.catch(function (e) { return handleError(e, vm, info + " (Promise/async)"); });
}
} catch (e) {
handleError(e, vm, info);
}
return res
}
Если событие срабатывает только один раз (т. е. с использованиемonceмодификатор), вызовcreateOnceHandlerАнонимный, удаляет привязку события после выполнения обратного вызова.
function createOnceHandler (event, handler, capture) {
var _target = target$1;
return function onceHandler () {
//调用事件回调
var res = handler.apply(null, arguments);
if (res !== null) {
// 移除事件绑定
remove$2(event, onceHandler, capture, _target);
}
}
}
addа такжеremoveдействительно вDOMПроцесс привязки событий и отвязки событий, его реализация также использует нативныеDOMизaddEventListener,removeEventListener api.
function add (name,handler,capture,passive){
···
target$1.addEventListener(name,handler,
supportsPassive
? { capture: capture, passive: passive }
: capture);
}
function remove (name,handler,capture,_target) {
(_target || target$1).removeEventListener(
name,
handler._wrapper || handler,
capture
);
}
Кроме того, отвязка событий происходит не только в событиях, которые срабатывают только один раз, но и в обновлениях компонентов.patchVnodeПроцесс, без конкретного анализа, вы можете обратиться к предыдущему исследованию контента по обновлению компонента.updateListenersпроцесс.
9.4 Пользовательские события
VueКак обращаться с роднымDomБыл описан основной поток событий, но есть важная концепция для событий, которые нельзя игнорировать, то есть пользовательские события компонентов. Мы знаем, что родительские и дочерние компоненты могут использовать события для связи, а дочерние компоненты передаютvm.$emitРаспределить события на родительский компонент, родительский компонент передаетv-on:(event)Получайте информацию и обрабатывайте обратные вызовы. Таким образом, естественно, в исходном коде для пользовательских событий предусмотрена другая логика обработки. Начнем с простого примера.
<script>
var child = {
template: `<div @click="emitToParent">点击传递信息给父组件</div>`,
methods: {
emitToParent() {
this.$emit('myevent', 1)
}
}
}
new Vue({
el: '#app',
components: {
child
},
template: `<div id="app"><child @myevent="myevent" @click.native="nativeClick"></child></div>`,
methods: {
myevent(num) {
console.log(num)
},
nativeClick() {
console.log('nativeClick')
}
}
})
</script>
Как видно из примера, обычные узлы могут использовать только нативныеDOMсобытия, в то время как пользовательские события и нативныеDOMсобытие и черезnativeРазличие модификаторов с нативнымDOMЧто касается основ обработки событий, давайте посмотрим, что такого особенного в пользовательских событиях.
9.4.1 Компиляция шаблона
Вернёмся и посмотрим на шаблонную компиляцию события, в сгенерированномASTСтадия дерева, как было проанализировано ранееaddHandlerМетод будет обрабатывать модификаторы события по-разному.nativeмодификатор, методы свойств, связанные с событиями, добавляются кnativeEventsв свойствах.
На картинке нижеchildСгенерированоASTДерево:
9.4.2 Генерация кода
Будь то компонент или обычная метка, код обработки событий находится вgenDataВ процессе, в соответствии с предыдущим анализом местных событий,genHandlersИспользуется для обработки объектов событий и объединения их в строки.
function genData() {
···
if (el.events) {
data += (genHandlers(el.events, false)) + ",";
}
if (el.nativeEvents) {
data += (genHandlers(el.nativeEvents, true)) + ",";
}
}
getHandlersЛогика была упомянута ранее, разница между обработкой собственных событий компонента и пользовательских событий заключается в следующем.isNativeС точки зрения опций мы видим, что окончательный сгенерированный код:
with(this){return _c('div',{attrs:{"id":"app"}},[_c('child',{on:{"myevent":myevent},nativeOn:{"click":function($event){return nativeClick($event)}}})],1)}
имеютrenderЗатем из него создается функцияVnodeэкземпляр, в котором дочерние компоненты создаются при обнаружении узла-заполнителя компонентаVnode, в это время дляon,nativeOnПроизводится специальное преобразование, котороеnativeOnназначить наon, чтобы последующий метод обработки был таким же, как и у обычных узлов. В дополнениеonназначить наlisteners, при созданииVNodeконфигурация компонентаcomponentOptionsвходящий.
// 创建子组件过程
function createComponent (){
···
var listeners = data.on;
// replace with listeners with .native modifier
// so it gets processed during parent component patch.
data.on = data.nativeOn;
···
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
);
return vnode
}
9.4.3 Экземпляр подкомпонента
Далее черезVnodeПроцесс создания реальных узлов, этот процесс сталкивается с дочерними элементамиVnodeПримеры будут аннулировать экземпляр компонента. Параметры инициализации процесса Ранее настроили конструкцию подклассов экземпляров процесса. Строитель конструктора обратно в анализ статьи, анализ временной серии в началеVue.prototype.initПроцесс пропуска процесса инициализации компонента, в котором ключ к обработке пользовательских событий выглядит следующим образом
Vue.prototype._init = function(options) {
···
// 针对子组件的事件处理逻辑
if (options && options._isComponent) {
// 初始化内部组件
initInternalComponent(vm, options);
} else {
// 选项合并,将合并后的选项赋值给实例的$options属性
vm.$options = mergeOptions(
resolveConstructorOptions(vm.constructor),
options || {},
vm
);
}
// 初始化事件处理
initEvents(vm);
}
function initInternalComponent (vm, options) {
var opts = vm.$options = Object.create(vm.constructor.options);
···
opts._parentListeners = vnodeComponentOptions.listeners;
···
}
На этом этапе дочерний компонент получает определение родительского узла-заполнителя.@myevent="myevent"мероприятие. Далее выполняется обработка события инициализации подкомпонента, в это времяvm.$options._parentListenersОн получит события, настроенные родительским компонентом. в то время как компонент с пользовательским событием будет выполнятьсяupdateComponentListenersфункция.
function initEvents (vm) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
// init parent attached events
var listeners = vm.$options._parentListeners;
if (listeners) {
// 带有自定义事件属性的实例
updateComponentListeners(vm, listeners);
}
}
Затем вернитесь к предыдущему анализуupdateListenersпроцесс и роднойDOMРазница между событиями заключается в том, что методы добавления и удаления пользовательских событий различны.
var target = vm;
function add (event, fn) {
target.$on(event, fn);
}
function remove$1 (event, fn) {
target.$off(event, fn);
}
function updateComponentListeners (vm,listeners,oldListeners) {
target = vm;
updateListeners(listeners, oldListeners || {}, add, remove$1, createOnceHandler, vm);
target = undefined;
}
9.4.4 API событий
давайте оглянемся назадVueКакие операции инициализации также выполняются при обработке событий на этапе введения.Vueиспользовать_eventsХранилище атрибутов управляет отправкой и обновлением событий, раскрывая$on, $once, $off, $emitМетоды для внешнего управления событиями и отправки событий выполнения.
eventsMixin(Vue); // 定义事件相关函数
function eventsMixin (Vue) {
var hookRE = /^hook:/;
// $on方法用来监听事件,执行回调
Vue.prototype.$on = function (event, fn) {
var vm = this;
// event支持数组形式。
if (Array.isArray(event)) {
for (var i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn);
}
} else {
// _events数组中记录需要监听的事件以及事件触发的回调
(vm._events[event] || (vm._events[event] = [])).push(fn);
if (hookRE.test(event)) {
vm._hasHookEvent = true;
}
}
return vm
};
// $once方法用来监听一次事件,执行回调
Vue.prototype.$once = function (event, fn) {
var vm = this;
// 对fn做一层包装,先解除绑定再执行fn回调
function on () {
vm.$off(event, on);
fn.apply(vm, arguments);
}
on.fn = fn;
vm.$on(event, on);
return vm
};
// $off方法用来解除事件监听
Vue.prototype.$off = function (event, fn) {
var vm = this;
// 如果$off方法没有传递任何参数时,将_events属性清空。
if (!arguments.length) {
vm._events = Object.create(null);
return vm
}
// 数组处理
if (Array.isArray(event)) {
for (var i$1 = 0, l = event.length; i$1 < l; i$1++) {
vm.$off(event[i$1], fn);
}
return vm
}
var cbs = vm._events[event];
if (!cbs) {
return vm
}
if (!fn) {
vm._events[event] = null;
return vm
}
// specific handler
var cb;
var i = cbs.length;
while (i--) {
cb = cbs[i];
if (cb === fn || cb.fn === fn) {
// 将监听的事件回调移除
cbs.splice(i, 1);
break
}
}
return vm
};
// $emit方法用来触发事件,执行回调
Vue.prototype.$emit = function (event) {
var vm = this;
{
var lowerCaseEvent = event.toLowerCase();
if (lowerCaseEvent !== event && vm._events[lowerCaseEvent]) {
tip(
"Event \"" + lowerCaseEvent + "\" is emitted in component " +
(formatComponentName(vm)) + " but the handler is registered for \"" + event + "\". " +
"Note that HTML attributes are case-insensitive and you cannot use " +
"v-on to listen to camelCase events when using in-DOM templates. " +
"You should probably use \"" + (hyphenate(event)) + "\" instead of \"" + event + "\"."
);
}
}
var cbs = vm._events[event];
// 找到已经监听事件的回调,执行
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
var args = toArray(arguments, 1);
var info = "event handler for \"" + event + "\"";
for (var i = 0, l = cbs.length; i < l; i++) {
invokeWithErrorHandling(cbs[i], vm, args, vm, info);
}
}
return vm
};
}
С этими API-интерфейсами событий добавление и удаление пользовательских событий становится намного проще для понимания. компоненты проходятthis.$emitСобытие отправляется в экземпляре компонента, а перед этим компонент добавил события и обратные вызовы, которые необходимо отслеживать, в экземпляр экземпляра._eventsВ свойстве, когда событие запускается, обратный вызов события прослушивания может быть выполнен напрямую.
Наконец, давайте рассмотрим связь между родительскими и дочерними компонентами под другим углом. Инициирование и мониторинг пользовательских событий компонента в основном выполняются в текущем экземпляре компонента. Причина, по которой может быть достигнута связь между родительскими и дочерними компонентами, заключается в том, что функция обратного вызова события мониторинг прописан в родительском компоненте.
9.5 Резюме
События являются важными функциональными точками в нашем ежедневном развитии,Vueвыставлены на прикладном уровне@,v-onдирективы для разработчиков по привязке событий в шаблонах. Директивы событий будут существовать в виде атрибутов на этапе компиляции шаблона и будут связывать связанные события в соответствии с атрибутами событий на этапе рендеринга реального узла. Для событий компонентов мы можем использовать события для связи между дочерними и родительскими компонентами. Он по существу поддерживает шину событий внутри одного и того же дочернего компонента. Из результатов анализа мы видим, что существует эффект связи между дочерними и родительскими компонентами. причина только в том, что функция обратного вызова написана в родительском компоненте.
- Углубленный анализ исходного кода Vue — слияние опций (включено)
- Углубленный анализ исходного кода Vue — слияние вариантов (ниже)
- Углубленный анализ исходного кода Vue — прокси данных, связывание дочерних и родительских компонентов
- Углубленный анализ исходного кода Vue — монтирование экземпляра, процесс компиляции
- Углубленный анализ исходного кода Vue — полный процесс рендеринга
- Углубленный анализ исходного кода Vue — основа компонентов
- Углубленный анализ исходного кода Vue — расширенный компонент
- Углубленный анализ исходного кода Vue — построение адаптивной системы (включено)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (посередине)
- Углубленный анализ исходного кода Vue — построение адаптивной системы (ниже)
- Углубленный анализ исходного кода Vue — приходите и реализуйте алгоритм сравнения вместе со мной!
- Углубленный анализ исходного кода Vue — демистификация механизма событий Vue
- Углубленный анализ исходного кода Vue - слоты Vue, все, что вы хотите знать, здесь!
- Углубленный анализ исходного кода Vue — понимаете ли вы синтаксический сахар v-model?
- Углубленный анализ исходного кода Vue — концепция динамических компонентов Vue, не запутаетесь ли вы?
- Тщательно изучите магию поддержки активности в Vue (часть 1)
- Тщательно изучите магию поддержки активности в Vue (часть 2)