说来惭愧,用 mpvue 大半年,小程序快一年了,居然还试图用 event.stopPropagation 方法阻止事件冒泡,也是有点蠢。痛定思痛,写篇博文来认真捋一捋小程序的事件系统和 mpvue 的事件代理 小程序事件系统,mpvue 事件系统和 DOM 事件系统的差别 从文档得知,小程序的 event 对象和 DOM 的 event 对象是不同的,我们还可以通过把两个对象的属性打印出来进行比较: // 小程序 event 对象属性(8 个) ["type", "timeStamp", "target", "currentTarget", "detail", "touches", "changedTouches", "_requireActive"] // DOM event 对象属性 / 方法(54 个) ["isTrusted", "screenX", "screenY", "clientX", "clientY", "ctrlKey", "shiftKey", "altKey", "metaKey", "button", "buttons", "relatedTarget", "pageX", "pageY", "x", "y", "offsetX", "offsetY", ..., "cancelable", "timeStamp", "srcElement", "returnValue", "cancelBubble", "path", "composedPath", "stopPropagation", "stopImmediatePropagation", "preventDefault", "initEvent"] // mpvue event 对象属性 / 方法(9 个) ["mp", "type", "timeStamp", "x", "y", "target", "currentTarget", "stopPropagation", "preventDefault"] 我们关注一下 stopPropagation 和 preventDefault 这两个在网页中常用的方法。从打印得到的属性来看,在小程序中的 event 对象中没有这两个方法,取而代之的是借助 catchtap 这样的语法来阻止事件冒泡。另外,小程序中没有什么方式来阻止默认事件。 但是在 mpvue 中,event 对象又被挂载了这两个方法。至于这两个方法是否可以真的有用,这就需要我们了解一下 mpvue 的事件代理机制。 mpvue 的事件代理 上面是mpvue生成的wxml文件片段,如果你看过 mpvue 的生成文件,可能会发现,在 mpvue 生成的 wxml 文件中,所有的事件都被一个叫 handleProxy 的函数接管,在 handleProxy 进行处理后再去调用我们写的真正的事件处理函数。这个方法在initMp时,作为小程序Page构造函数的一个选项,从而可以在wxml中被正确调用。函数的定义如下: // https://github.com/Meituan-Dianping/mpvue/blob/63700b2d4e4e678bc4297c71e3acd1f36647907a/src/platforms/mp/runtime/lifecycle.js#L286-L288 handleProxy (e) { return rootVueVM.$handleProxyWithVue(e) } // https://github.com/Meituan-Dianping/mpvue/blob/63700b2d4e4e678bc4297c71e3acd1f36647907a/src/platforms/mp/runtime/index.js#L53-L54 import { handleProxyWithVue } from './events' Vue.prototype.$handleProxyWithVue = handleProxyWithVue // handleProxyWithVue实现在 events.js 中 从上面的代码可以看出,handleProxy只是将小程序的event对象传给handleProxyWithVue函数进行进一步处理。接下来我们看看handleProxyWithVue都做了什么工作: // https://github.com/Meituan-Dianping/mpvue/blob/63700b2d4e4e678bc4297c71e3acd1f36647907a/src/platforms/mp/runtime/events.js#L84-L108 export function handleProxyWithVue (e) { const rootVueVM = this.$root const { type, target = {}, currentTarget } = e const { dataset = {}} = currentTarget || target const { comkey = '', eventid } = dataset const vm = getVM(rootVueVM, comkey.split(','))// 根据comkey找到页面对应的mpvue实例(vm) if (!vm) { return } const webEventTypes = eventTypeMap[type] || [type] const handles = getHandle(vm._vnode, eventid, webEventTypes) //找到真正的事件处理函数 if (handles.length) { const event = getWebEventByMP(e) // 包装为mpvue的event对象 if (handles.length === 1) { const result = handles[0](event) return result } handles.forEach(h => h(event))// 调用真实的事件处理函数 } } handleProxyWithVue做了以下几件事情: 从根实例开始,根据comkey找出事件处理函数所在的mpvue实例(getVm) 通过遍历找到的vm的vnode,结合eventid找到小程序事件对应的真实的事件处理函数(getHandle) 将小程序的event对象包装为mpvue的event对象(getWebEventByMP),并添加了preventDefault和stopPropagation方法 将包装后的event对象传给真实的处理函数进行调用 这就解释了生成的wxml中绑定事件的节点都有event-comkey和event-eventid属性,在一个事件触发时,它们是被用来寻找事件对应的vm实例和对应的事件处理函数的。 最后我们来看看getWebEventByMP函数: // https://github.com/Meituan-Dianping/mpvue/blob/63700b2d4e4e678bc4297c71e3acd1f36647907a/src/platforms/mp/runtime/events.js#L62-L82 function getWebEventByMP (e) { const { type, timeStamp, touches, detail = {}, target = {}, currentTarget = {}} = e const { x, y } = detail const event = { mp: e, type, timeStamp, x, y, target: Object.assign({}, target, detail), currentTarget, stopPropagation: noop, preventDefault: noop } if (touches && touches.length) { Object.assign(event, touches[0]) event.touches = touches } return event } 从上面代码可以看出,stopPropagation和preventDefault都对应到一个叫noop的函数,而这个函数是一个空函数,也就是说在mpvue中,尽管可以调用event.stopPropagation(),但是并没有什么用,还是要借助.stop修饰符通过compiler编译成catchEvent来阻止冒泡。(完) cnblogs-md-editor编辑器,一个改善博客园MarkDown编辑器的chrome 插件https://www.cnblogs.com/imgss/p/mpvue-handleProxy.html