Vue源码解读之事件机制
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源码解读之事件机制相关推荐
- 约2万字-Vue源码解读汇总篇(续更)
约2万字-Vue源码解读汇总篇(续更) 一.前言 1.系列汇总 未完待续... Vue源码解读:06Vue3探索篇 Vue源码解读:05生命周期篇 Vue源码解读:04模板编译篇 Vue源码解读:03 ...
- Vue 源码解读(11)—— render helper
当学习成为了习惯,知识也就变成了常识. 感谢各位的 关注.点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/b ...
- Vue源码解读(五):render和VNode
Vue 2.0 相比 Vue 1.0 最大的升级就是利用了虚拟DOM. 在 Vue 1.0 中视图的更新是纯响应式的.在进行响应式初始化的时候,一个响应式数据 key 会创建一个对应的 dep,这个 ...
- Vue 源码解读(12)—— patch
当学习成为了习惯,知识也就变成了常识. 感谢各位的 关注.点赞.收藏和评论. 新视频和文章会第一时间在微信公众号发送,欢迎关注:李永宁lyn 文章已收录到 github 仓库 liyongning/b ...
- Vue源码解读(六):update和patch
Vue 的 _update 是实例上的一个私有方法,主要的作用就是把 VNode 渲染成真实的 DOM ,它在首次渲染和数据更新的时候被调用.在数据更新的时候会发生新 VNode 和 旧 VNode ...
- Vue源码解读-1(世上无难事,只要肯放弃)
从19年开始由于一些原因好久没怎么写博客了,今天心血来潮记录下vue的源码解读历程(可能中间懒了就又停了,世上无难事,只要肯放弃嘛!) 正式开始 准备工作 我们把源码搞下来后,用vscode打开后不要 ...
- [Vue源码分析]自定义事件原理及事件总线的实现
最近小组有个关于vue源码分析的分享会,提前准备一下- 前言: 我们都知道Vue中父组件可以通过 props 向下传数据给子组件:子组件可以通过向$emit触发一个事件,在父组件中执行回调函数,从而实 ...
- Vue钩子函数中的this为什么能指向Vue的实例而不是指向传入的参数options(Vue源码解读)
起因 先看一段Vue的代码,在Vue的原型链上增加了一个setData方法,然后实例化Vue对象,传入一个Object类型的参数 Vue.prototype.setData = function (k ...
- html标签转化成vnode插件,vue源码解读之html与vNode间的转换
被compiler处理.compiler使用generate(ast, options)方法,将template转换为指定结构的对象. compiler编译 阅读源码逻辑可得,在实际上compiler ...
最新文章
- 编写linux下跑马灯应用程序,01 arm11 led 跑马灯程序
- 有抱负的Web开发人员应考虑的6件事
- 超级猩猩获 3.6 亿元 D 轮融资,计划今年新开100家门店
- 三十七、Redis和MongoDB基本语法
- python四舍五入round_四舍五入就用round( )?Python四舍五入的正确打开方式!
- java类中,成员变量赋值第一个进行,其次是静态构造函数,再次是构造函数
- rocksdb原理_教你玩转MyRocks/RocksDB—STATISTICS与后台线程篇
- java supplier_现代化的 Java (二十一)——宏和生成宏
- 基于proxychains4进行终端加速 wget,curl等
- 【前端知识梳理】HTML篇 笔记整理(一)
- 在线生成条形码(39码、EAN-13)
- 3 Idiots ——谢 阿米尔·汗
- 人民币对美元汇率中间价报6.7774元 下调109个基点
- 云开发—扫码点餐系统实战
- contents属性
- 惠普服务器蓝屏怎么修复,Win10惠普电脑出现蓝屏?解决教程附上
- 【新手入门】deepfacelab的电脑配置要求
- 中科院计算机网络信息中心是一种怎样的存在?
- matlab怎么添加条纹噪声,基于频域的图像条纹噪声消除方法
- 【初级C语言】表达式和基本语句(布尔型与0比较,浮点型与0比较,switch语句,提高循环语句的效率)
热门文章
- python灰度处理_python 简单图像处理(14) 灰度图腐蚀和膨胀,开运算、闭运算...
- spring学习--AOP术语
- 恭喜宿主获得鸿蒙,第四十章大殿讲道,十连抽获得鸿蒙至宝!
- pythonmysql数据分析_利用Mysql进行python的数据分析
- pythontcp服务器如何关闭阻塞_python 网络编程(socketserver,阻塞,其他方法)
- 如何创建_重庆市百科如何创建
- ge linux安装apt_教你如何在 Linux 中使用 apt 命令
- README.md怎么写比较好
- 灰度重采样的方法分为_遥感导论-期末试卷及答案
- esxi命令关机虚拟机_虚拟机镜像使用说明