Vue 源码
你想要的 vue 源码分析 · 30
DOM 事件与自定义事件如何用 DOM 事件与自定义事件从编译到生成事件对象的阶段 编译 addHandler 生成代码数据 DOM 事件执行时做了什么 自定义事件都做了什么 父子组件通信的实质 子组件通过父组件传入 listener,去通过$on方式去添加,当$emit 的时候会去执行这个添加的函数。 $emit是往自身派发事件,$emit 是往 vue 实例派发事件,子组件调用$emit 方法…
DOM 事件与自定义事件如何用 DOM 事件与自定义事件从编译到生成事件对象的阶段 编译 addHandler 生成代码数据 DOM 事件执行时做了什么 自定义事件都做了什么 父子组件通信的实质 子组件通过父组件传入 listener,去通过$on方式去添加,当$emit 的时候会去执行这个添加的函数。 $emit是往自身派发事件,$emit 是往 vue 实例派发事件,子组件调用$emit 方法…
let child = {
template: `<div @click="handler($event)">div</div>`,
methods: {
handler(e) {
console.log(e);
this.$emit("select");
}
}
};
var father = {
el: "#app",
template: `<div @select="select"><Child @select="select" @click.native.prevent="handler"></Child></div>`,
methods: {
handler() {
console.log("Child clicked!");
},
select() {
console.log("Child select!");
}
},
components: {
Child
}
};
// src/compiler/parser/index.js
export const onRE = /^@|^v-on:/;
export const dirRE = /^v-|^@|^:/;
export const bindRE = /^:|^v-bind:/;
function parseModifiers(name: string): Object | void {
const match = name.match(modifierRE);
if (match) {
const ret = {};
match.forEach((m) => {
ret[m.slice(1)] = true;
});
return ret;
}
}
function processAttrs(el) {
const list = el.attrsList;
let 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;
// 对标签属性进行处理,如果是指令(v-),先解析出修饰符
if (dirRE.test(name)) {
// mark element as dynamic
el.hasBindings = true;
// 把修饰符转换成对象
modifiers = parseModifiers(name);
if (modifiers) {
name = name.replace(modifierRE, "");
}
if (bindRE.test(name)) {
// v-bind
} else if (onRE.test(name)) {
// 如果是事件指令
name = name.replace(onRE, "");
addHandler(el, name, value, modifiers, false, warn);
} else {
//
}
} else {
//
}
}
}
// src/compiler/helpers.js
// 做了三件事:1、处理修饰符对事件名处理,2、根据修饰符判断是原生事件,还是普通事件,3、缓存回调函数,调用时候用
export function addHandler(el: ASTElement, name: string, value: string, modifiers: ?ASTModifiers, important?: boolean, warn?: Function) {
modifiers = modifiers || emptyObject;
// warn prevent and passive modifier
/* istanbul ignore if */
if (process.env.NODE_ENV !== "production" && warn && modifiers.prevent && modifiers.passive) {
warn("passive and prevent can't be used together. " + "Passive handler can't prevent default event.");
}
// 1、处理修饰符对事件名处理
if (modifiers.capture) {
delete modifiers.capture;
name = "!" + name; // mark the event as captured
}
if (modifiers.once) {
delete modifiers.once;
name = "~" + name; // mark the event as once
}
/* istanbul ignore if */
if (modifiers.passive) {
delete modifiers.passive;
name = "&" + name; // mark the event as passive
}
// normalize click.right and click.middle since they don't actually fire
// this is technically browser-specific, but at least for now browsers are
// the only target envs that have right/middle clicks.
if (name === "click") {
if (modifiers.right) {
name = "contextmenu";
delete modifiers.right;
} else if (modifiers.middle) {
name = "mouseup";
}
}
// 2、根据修饰符判断是原生事件,还是普通事件
let events;
if (modifiers.native) {
delete modifiers.native;
events = el.nativeEvents || (el.nativeEvents = {});
} else {
events = el.events || (el.events = {});
}
const newHandler: any = {
value: value.trim()
};
if (modifiers !== emptyObject) {
newHandler.modifiers = modifiers;
}
// 3、缓存回调函数,调用时候用
const 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;
}
// 以上例为例,最后生成
// 子组件
el.events = {
click: {
value: "click($event)"
}
};
// 父组件
el.events = {
select: {
value: "select"
}
};
el.nativeEvents = {
click: {
value: "handler",
modifiers: {
prevent: true
}
}
};
// src/compiler/codegen/index.js
// 根据events和nativeEvents生成data数据
export function genData(el: ASTElement, state: CodegenState): string {
let data = "{";
// ...
if (el.events) {
data += `${genHandlers(el.events, false, state.warn)},`;
}
if (el.nativeEvents) {
data += `${genHandlers(el.nativeEvents, true, state.warn)},`;
}
// ...
return data;
}
// src/compiler/codegen/events.js
// 修饰符不存在,如果handler.value是函数调用路径或者函数函数表达式,return handler.value
// 修饰符存在,根据handler.value,处理一下修饰符,最终返回一个函数表达式
export function genHandlers(events: ASTElementHandlers, isNative: boolean, warn: Function): string {
let res = isNative ? "nativeOn:{" : "on:{";
for (const name in events) {
res += `"${name}":${genHandler(name, events[name])},`;
}
return res.slice(0, -1) + "}";
}
const fnExpRE = /^\s*([\w$_]+|\([^)]*?\))\s*=>|^function\s*\(/;
const simplePathRE = /^\s*[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*|\['.*?']|\[".*?"]|\[\d+]|\[[A-Za-z_$][\w$]*])*\s*$/;
function genHandler(name: string, handler: ASTElementHandler | Array<ASTElementHandler>): string {
if (!handler) {
return "function(){}";
}
if (Array.isArray(handler)) {
return `[${handler.map((handler) => genHandler(name, handler)).join(",")}]`;
}
const isMethodPath = simplePathRE.test(handler.value);
const isFunctionExpression = fnExpRE.test(handler.value);
if (!handler.modifiers) {
if (isMethodPath || isFunctionExpression) {
return handler.value;
}
return `function($event){${handler.value}}`; // inline statement
} else {
let code = "";
let genModifierCode = "";
const keys = [];
for (const key in handler.modifiers) {
if (modifierCode[key]) {
genModifierCode += modifierCode[key];
// left/right
if (keyCodes[key]) {
keys.push(key);
}
} else if (key === "exact") {
const modifiers: ASTModifiers = (handler.modifiers: any);
genModifierCode += genGuard(
["ctrl", "shift", "alt", "meta"]
.filter((keyModifier) => !modifiers[keyModifier])
.map((keyModifier) => `$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;
}
const handlerCode = isMethodPath ? `return ${handler.value}($event)` : isFunctionExpression ? `return (${handler.value})($event)` : handler.value;
return `function($event){${code}${handlerCode}}`;
}
}
// 最终生成
// 父组件
{
on: {"select": select},
nativeOn: {"click": function($event) {
$event.preventDefault();
return handler($event)
}
}
}
// 子组件
{
on: {"click": function($event) {
handler($event)
}
}
}
// src/platforms/web/runtime/modules/event.js
// patch的更新与创建会执行updateDOMListeners
let target: any;
function updateDOMListeners(oldVnode: VNodeWithData, vnode: VNodeWithData) {
if (isUndef(oldVnode.data.on) && isUndef(vnode.data.on)) {
return;
}
// 获取事件对象
const on = vnode.data.on || {};
const oldOn = oldVnode.data.on || {};
// 缓存vnode的dom对象
target = vnode.elm;
// 遍历on加事件监听,遍历oldOn移除事件监听,事件可以是原生dom事件,也可以是自定义事件
updateListeners(on, oldOn, add, remove, vnode.context);
target = undefined;
}
// src/core/vdom/helpers/update-listeners.js
export function updateListeners(on: Object, oldOn: Object, add: Function, remove: Function, vm: Component) {
let name, def, cur, old, event;
// 遍历on加事件监听
for (name in on) {
def = cur = on[name];
old = oldOn[name];
// 判断事件是否有once、capture、passive 等修饰符
event = normalizeEvent(name);
if (isUndef(cur)) {
process.env.NODE_ENV !== "production" && warn(`Invalid handler for event "${event.name}": got ` + String(cur), vm);
} else if (isUndef(old)) {
if (isUndef(cur.fns)) {
// 通过保留引用关系,保证事件回调只添加一次,以后修改只需要修改回调函数的引用
cur = on[name] = createFnInvoker(cur);
}
// 完成事件绑定,用addeventListener添加事件,如果在添加dom事件回调函数之间执行,会把更改的推入队列,当作macroTask任务在nextTick执行
add(event.name, cur, event.once, event.capture, event.passive, event.params);
} else if (cur !== old) {
old.fns = cur;
on[name] = old;
}
}
// 遍历oldOn移除事件监听
for (name in oldOn) {
if (isUndef(on[name])) {
event = normalizeEvent(name);
// 移除事件绑定,用removeEventListener移除事件,如果在移除dom事件回调函数之间执行,会把更改的推入队列,当作macroTask任务在nextTick执行
remove(event.name, oldOn[name], event.capture);
}
}
}
// 判断事件是否有once、capture、passive 等修饰符
// src/core/vdom/helpers/update-listeners.js
const normalizeEvent = cached(
(
name: string
): {
name: string,
once: boolean,
capture: boolean,
passive: boolean,
handler?: Function,
params?: Array<any>
} => {
const passive = name.charAt(0) === "&";
name = passive ? name.slice(1) : name;
const once = name.charAt(0) === "~"; // Prefixed last, checked first
name = once ? name.slice(1) : name;
const capture = name.charAt(0) === "!";
name = capture ? name.slice(1) : name;
return {
name,
once,
capture,
passive
};
}
);
// 通过保留引用关系,保证事件回调只添加一次,以后修改只需要修改回调函数的引用
// src/core/vdom/helpers/update-listeners.js
export function createFnInvoker(fns: Function | Array<Function>): Function {
function invoker() {
const fns = invoker.fns;
if (Array.isArray(fns)) {
const cloned = fns.slice();
for (let i = 0; i < cloned.length; i++) {
cloned[i].apply(null, arguments);
}
} else {
return fns.apply(null, arguments);
}
}
invoker.fns = fns;
return invoker;
}
// src/core/vdom/create-component.js
// 首先创建一个vnode
export function createComponent(Ctor: Class<Component> | Function | Object | void, data: ?VNodeData, context: Component, children: ?Array<VNode>, tag?: string): VNode | Array<VNode> | void {
// ...
const listeners = data.on;
// 获取自定义事件
data.on = data.nativeOn;
const name = Ctor.options.name || tag;
// 把listeners,作为生成vnode的一个参数传入,处理环境是子组件,
const vnode = new VNode(`vue-component-${Ctor.cid}${name ? `-${name}` : ""}`, data, undefined, undefined, undefined, context, { Ctor, propsData, listeners, tag, children }, asyncFactory);
return vnode;
}
// src/core/instance/init.js
// 先初始化下父组件传入的listener
export function initInternalComponent(vm: Component, options: InternalComponentOptions) {
const opts = (vm.$options = Object.create(vm.constructor.options));
// ....
const vnodeComponentOptions = parentVnode.componentOptions;
opts._parentListeners = vnodeComponentOptions.listeners;
// ...
}
// src/core/instance/events.js
// 带一些参数中转下listeners
export function initEvents(vm: Component) {
vm._events = Object.create(null);
vm._hasHookEvent = false;
// init parent attached events
const listeners = vm.$options._parentListeners;
if (listeners) {
updateComponentListeners(vm, listeners);
}
}
// 然后对自定义事件进行add和remove
// src/core/instance/events.js
let target: any;
export function updateComponentListeners(vm: Component, listeners: Object, oldListeners: ?Object) {
target = vm;
updateListeners(listeners, oldListeners || {}, add, remove, vm);
target = undefined;
}
// 自定义事件的add和remove,是通过vue的事件中心实现的
// src/core/instance/events.js
function add(event, fn, once) {
if (once) {
target.$once(event, fn);
} else {
target.$on(event, fn);
}
}
function remove(event, fn) {
target.$off(event, fn);
}
// 自定义事件的监听与移除监听用 vue 的事件中心实现的
// 1. 执行$on 先把事件回调函数按照事件名存起来
// 2. 执行$emit 根据事件名,找到回调函数,遍历执行回调函数
// 3. 执行$off 移除事件名和事件名对应的回调函数
// 4. 执行$once, 执行$on后再执行$off
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 {
// 1. 执行$on 先把事件按照事件名称存起来
(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;
};
// 4. 执行$once, 执行$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;
};
// 3. 执行$off 移除事件名和事件名对应的回调函数
Vue.prototype.$off = function(event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this;
// all
if (!arguments.length) {
vm._events = Object.create(null);
return vm;
}
// array of events
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
this.$off(event[i], fn);
}
return vm;
}
// specific event
const cbs = vm._events[event];
if (!cbs) {
return vm;
}
if (!fn) {
vm._events[event] = null;
return vm;
}
if (fn) {
// specific handler
let cb;
let i = cbs.length;
while (i--) {
cb = cbs[i];
if (cb === fn || cb.fn === fn) {
cbs.splice(i, 1);
break;
}
}
}
return vm;
};
// 2. 执行$emit 根据事件名,找到回调函数,遍历执行回调函数
Vue.prototype.$emit = function(event: string): Component {
const vm: Component = this;
if (process.env.NODE_ENV !== "production") {
const 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}".`);
}
}
let cbs = vm._events[event];
if (cbs) {
cbs = cbs.length > 1 ? toArray(cbs) : cbs;
const args = toArray(arguments, 1);
for (let 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;
};
}
- 子组件通过父组件传入 listener,去通过$on方式去添加,当$emit 的时候会去执行这个添加的函数。
- $emit是往自身派发事件,$emit 是往 vue 实例派发事件,子组件调用$emit 方法的时候,往当前子组件派发事件,因为找的是当前子组件的 events,为什么会有父子组件通信,是因为回调函数是定义的父组件中,实际上却是在子组件实例上监听这个自定义事件,父组件的 listener 会在子组件创建中传入,在子组件初始化中,拿到父组件传入的事件回调,事件回调又定义在父组件的环境中,当子组件自身派发这个事件的时候,回调就执行了,回调是父环境,所以就达成了父子组件通信
暂无评论
确认操作
请再次确认编辑图片
待上传图片 (剩余 0 张)
项目详情
确认评论邮箱
邀请制评论