Vue定义了四种添加事件监听的方法:

例如$on,可通过vm.$on(event,callback)添加监听。

事件监听

$on

Vue.prototype.$on = function (event, fn) {var this$1 = this;var vm = this;//如果传参event是数组,递归调用$onif (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);// 这里在注册事件的时候标记bool值也就是个标志位来表明存在钩子,而不需要通过哈希表的方法来查找是否有钩子,这样做可以减少不必要的开销,优化性能。if (hookRE.test(event)) {vm._hasHookEvent = true;}}return vm};

$once

$once监听只能触发一次的事件,触发以后会自动移除该事件。

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};

$off

$off用来移除自定义事件。

  Vue.prototype.$off = function (event, fn) {var this$1 = this;var vm = this;// 如果没有参数,关闭全部事件监听器if (!arguments.length) {vm._events = Object.create(null);return vm}// 关闭数组中的事件监听器if (Array.isArray(event)) {for (var i = 0, l = event.length; i < l; i++) {this$1.$off(event[i], fn);}return vm}// 具体的某个事件var cbs = vm._events[event];if (!cbs) {return vm}//  fn回调函数不存在,将事件监听器变为null,返回vmif (!fn) {vm._events[event] = null;return vm}// 回调函数存在if (fn) {// specific handlervar cb;var i$1 = cbs.length;while (i$1--) {cb = cbs[i$1];if (cb === fn || cb.fn === fn) {// 移除 fn 这个事件监听器cbs.splice(i$1, 1);break}}}return vm};

$emit

$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);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};

HTML元素上点击事件

先来研究发生在原生dom元素上的事件。
用v-on指令监听DOM事件,并在触发时运行一些JS代码,也可以通过@简写的方式,原因:
Vue定义了这样的正则表达式var onRE = /^@|^v-on:/;,在processAttrs()解析属性时通过onRE.test(name)判断来添加属性。
html的编写如下:

<body>
<div id="app"><button @click="test01">点我01号</button>
</div>
<script>new Vue({'el':'#app',methods:{test01:function(){alert('01号被点了!');}}});
</script>
</body>

Vue初始化时,调用initEvents(vm)对事件进行初始化:

function initEvents (vm) {vm._events = Object.create(null);//_hasHookEvent标志位表明是否存在钩子,而不需要通过哈希表来查找是否有钩子,这样做可以减少不必要的开销,优化性能。vm._hasHookEvent = false;//初始化父组件attach的事件var listeners = vm.$options._parentListeners;if (listeners) {updateComponentListeners(vm, listeners);}
}

initEvents方法在vm上创建一个_events对象,用来存放事件。
接下来就调用initState()方法初始化事件,相关代码段:
if (opts.methods) { initMethods(vm, opts.methods); }
这个例子methods内有方法test01,因此会执行initMethods。

vm[key] = methods[key] == null ? noop : bind(methods[key], vm);

initMethods遍历定义的methods,通过调用bind改变函数的this指向,修饰了事件的回调函数,组件上挂载的事件都是在父作用域中的。

function nativeBind (fn, ctx) {//fn的this指向ctxreturn fn.bind(ctx)
}
var bind = Function.prototype.bind? nativeBind: polyfillBind;

这里bind用来改变函数中this的指向。(关于call,apply,bind的学习来自于https://www.cnblogs.com/xljzl...)
接下来Vue将html解析成ast,解析后的render如图所示


解析过程参考https://segmentfault.com/a/11...
接下来调用add$1通过addEventListener将事件绑定到target上。

function add$1 (event,handler,once$$1,apture,passive) {handler = withMacroTask(handler);if (once$$1) { handler = createOnceHandler(handler, event, capture); }//绑定点击事件target$1.addEventListener(event,handler,supportsPassive? { capture: capture, passive: passive }: capture);
}

component上点击事件

<body>
<div id="app"><child-test :test-message="mess" v-on:click.native="test01" v-on:componenton="test02"></child-test>
</div>
<template id="tplt01"><button>{{testMessage}}</button>
</template>
<script>new Vue({'el':'#app',data:{mess:'组件点击001'},methods:{test01:function(){alert('01号被点了!');},test02:function(){alert('01号被点了!');}},components:{'childTest':{template:"#tplt01",props:['testMessage'],methods:{test02:function(){alert('001号被点了!');}},}}});
</script>
</body>

<child-test>标签定义了click事件,若click事件没加修饰符.native,点击按钮不出发任何事件,
若添加了.native修饰符,点击按钮执行的就是test01,alert('01号被点了!');。.native的作用就是在原生dom上绑定事件。
不添加.native的的事件解析过程与上文相同,而添加了.native之后,v-on的事件会被放到nativeOn数组中,解析后的render如图所示:

在事件初始化,调用genHandlers的时候,会先判断该事件是否为native,如果是,解析的事件字符串就会用'nativeOn{}'包裹。

function genHandlers (events,isNative,warn
) {var res = isNative ? 'nativeOn:{' : 'on:{';for (var name in events) {res += "\"" + name + "\":" + (genHandler(name, events[name])) + ",";}return res.slice(0, -1) + '}'
}

html解析成vnode之后会调用createComponent进行处理。

function createComponent (Ctor,data,context,children,tag
) {if (isUndef(Ctor)) {return}var baseCtor = context.$options._base;/*其他代码省略*///缓存data.on的函数,这些需要作为子组件监听器而不是DOM监听器来处理。就是componenton事件var listeners = data.on;//data.on被native修饰符的事件所替换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
}

createComponent将.native修饰符的事件放在data.on上面。接下来data.on上的事件(本文中的alert('001号被点了!');)会按普通的html事件往下走。

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);updateListeners(on, oldOn, add$1, remove$2, vnode.context);target$1 = undefined;
}

最终通过target$1.addEventListener添加事件监听。
而标签内没有.native的修饰符调用的是$on方法。

function updateComponentListeners (vm,listeners,oldListeners
) {target = vm;updateListeners(listeners, oldListeners || {}, add, remove$1, vm);target = undefined;
}

updateListeners又调用了add方法

function add (event, fn, once) {if (once) {target.$once(event, fn);} else {target.$on(event, fn);}
}

也就是说对于普通html元素和在组件标签内添加了.native修饰符的事件,都通过target$1.addEventListener()来挂载事件。而定义在组件上的事件会调用原型上的$on等方法。

事件修饰符及其他

关于事件的使用官网已经说得很清楚啦,这里就不赘述啦。
https://cn.vuejs.org/v2/guide...

<!-- 阻止单击事件继续传播 -->
<a v-on:click.stop="doThis"></a><!-- 提交事件不再重载页面 -->
<form v-on:submit.prevent="onSubmit"></form><!-- 修饰符可以串联 -->
<a v-on:click.stop.prevent="doThat"></a><!-- 只有修饰符 -->
<form v-on:submit.prevent></form><!-- 添加事件监听器时使用事件捕获模式 -->
<!-- 即元素自身触发的事件先在此处处理,然后才交由内部元素进行处理 -->
<div v-on:click.capture="doThis">...</div><!-- 只当在 event.target 是当前元素自身时触发处理函数 -->
<!-- 即事件不是从内部元素触发的 -->
<div v-on:click.self="doThat">...</div>

Vue源码解读之事件机制相关推荐

  1. 约2万字-Vue源码解读汇总篇(续更)

    约2万字-Vue源码解读汇总篇(续更) 一.前言 1.系列汇总 未完待续... Vue源码解读:06Vue3探索篇 Vue源码解读:05生命周期篇 Vue源码解读:04模板编译篇 Vue源码解读:03 ...

  2. Vue 源码解读(11)—— render helper

    当学习成为了习惯,知识也就变成了常识. 感谢各位的 关注.点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/b ...

  3. Vue源码解读(五):render和VNode

    Vue 2.0 相比 Vue 1.0 最大的升级就是利用了虚拟DOM. 在 Vue 1.0 中视图的更新是纯响应式的.在进行响应式初始化的时候,一个响应式数据 key 会创建一个对应的 dep,这个 ...

  4. Vue 源码解读(12)—— patch

    当学习成为了习惯,知识也就变成了常识. 感谢各位的 关注.点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/b ...

  5. Vue源码解读(六):update和patch

    Vue 的 _update 是实例上的一个私有方法,主要的作用就是把 VNode 渲染成真实的 DOM ,它在首次渲染和数据更新的时候被调用.在数据更新的时候会发生新 VNode 和 旧 VNode ...

  6. Vue源码解读-1(世上无难事,只要肯放弃)

    从19年开始由于一些原因好久没怎么写博客了,今天心血来潮记录下vue的源码解读历程(可能中间懒了就又停了,世上无难事,只要肯放弃嘛!) 正式开始 准备工作 我们把源码搞下来后,用vscode打开后不要 ...

  7. [Vue源码分析]自定义事件原理及事件总线的实现

    最近小组有个关于vue源码分析的分享会,提前准备一下- 前言: 我们都知道Vue中父组件可以通过 props 向下传数据给子组件:子组件可以通过向$emit触发一个事件,在父组件中执行回调函数,从而实 ...

  8. Vue钩子函数中的this为什么能指向Vue的实例而不是指向传入的参数options(Vue源码解读)

    起因 先看一段Vue的代码,在Vue的原型链上增加了一个setData方法,然后实例化Vue对象,传入一个Object类型的参数 Vue.prototype.setData = function (k ...

  9. html标签转化成vnode插件,vue源码解读之html与vNode间的转换

    被compiler处理.compiler使用generate(ast, options)方法,将template转换为指定结构的对象. compiler编译 阅读源码逻辑可得,在实际上compiler ...

最新文章

  1. 编写linux下跑马灯应用程序,01 arm11 led 跑马灯程序
  2. 有抱负的Web开发人员应考虑的6件事
  3. 超级猩猩获 3.6 亿元 D 轮融资,计划今年新开100家门店
  4. 三十七、Redis和MongoDB基本语法
  5. python四舍五入round_四舍五入就用round( )?Python四舍五入的正确打开方式!
  6. java类中,成员变量赋值第一个进行,其次是静态构造函数,再次是构造函数
  7. rocksdb原理_教你玩转MyRocks/RocksDB—STATISTICS与后台线程篇
  8. java supplier_现代化的 Java (二十一)——宏和生成宏
  9. 基于proxychains4进行终端加速 wget,curl等
  10. 【前端知识梳理】HTML篇 笔记整理(一)
  11. 在线生成条形码(39码、EAN-13)
  12. 3 Idiots ——谢 阿米尔·汗
  13. 人民币对美元汇率中间价报6.7774元 下调109个基点
  14. 云开发—扫码点餐系统实战
  15. contents属性
  16. 惠普服务器蓝屏怎么修复,Win10惠普电脑出现蓝屏?解决教程附上
  17. 【新手入门】deepfacelab的电脑配置要求
  18. 中科院计算机网络信息中心是一种怎样的存在?
  19. matlab怎么添加条纹噪声,基于频域的图像条纹噪声消除方法
  20. 【初级C语言】表达式和基本语句(布尔型与0比较,浮点型与0比较,switch语句,提高循环语句的效率)

热门文章

  1. python灰度处理_python 简单图像处理(14) 灰度图腐蚀和膨胀,开运算、闭运算...
  2. spring学习--AOP术语
  3. 恭喜宿主获得鸿蒙,第四十章大殿讲道,十连抽获得鸿蒙至宝!
  4. pythonmysql数据分析_利用Mysql进行python的数据分析
  5. pythontcp服务器如何关闭阻塞_python 网络编程(socketserver,阻塞,其他方法)
  6. 如何创建_重庆市百科如何创建
  7. ge linux安装apt_教你如何在 Linux 中使用 apt 命令
  8. README.md怎么写比较好
  9. 灰度重采样的方法分为_遥感导论-期末试卷及答案
  10. esxi命令关机虚拟机_虚拟机镜像使用说明