Vue实例方法之事件的实现
开始
这段时间一直在看vue的源码,源码非常多和杂,所以自己结合资料和理解理出了一个主线,然后根据主线去剥离其他的一些知识点,然后将各个知识点逐一学习。这里主要是分析的Vue事件处理的实现。
正文
一、了解使用方式
在分析之前先了解下几个api的使用方式:
vm.$on(event, callback)
参数:
{string | Array<string>} event(数组只在 2.2.0+ 中支持){Function} callback
- 用法:
$on事件需要两个参数,一个是监听的当前实例上的事件名,一个是事件触发的回调函数,回调函数接受的是在事件出发的时候额外传递的参数。 - 例子:
vm.$on('test', function (msg) {
console.log(msg)
})
vm.$emit('test', 'hi')
// => "hi"
vm.$once(event, callback)
$once事件整体上来说和$on事件的使用方式差不多,但是event只支持字符串也就是说只支持单个事件。并且该事件再触发一次后就移除了监听器。
- 例子
vm.$once('testonce', function (msg) {
console.log(msg)
})
vm.$off([event, callback])
参数:
{string | Array<string>} event(仅在 2.2.2+ 支持数组){Function} [callback]
用法:移除自定义事件监听器
- 如果没有提供参数,则移除所有的事件监听器
- 如果只提供了事件,则移除该事件所有的监听器;
- 如果同时提供了事件与回调,则只移除这个回调的监听器。
- 例子:
vm.$off()
vm.$off('test')
vm.$off('test1', function (msg) {
console.log(msg)
})
vm.$off(['test1','test2'], function (msg) {
console.log(msg)
})
vm.$emit(event, [..args])
参数:
{string} event要触发的事件名[...args]可选
- 用法:
触发当前实例上的事件。附加参数都会传给监听器回调。
- 例子
vm.$emit('test', '触发自定义事件')
二、源码分析
事件的初始化工作
我们在使用自定义事件的api的时候,肯定有个地方是需要来存我们的事件和回调的地方。在vue中大部分的初始化工作都是在core/instance/init.js的initMixin方法中。所以我们能够在initMixin看到initEvents方法。
// initEvents
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)
}
}
上面的代码可以看到,在初始化Vue事件的时候,在vm实例上面挂载了一个_events的空对象。后面我们自己调用的自定义事件都存在里面。
因为vue本身在组件嵌套的时候就有子组件使用父组件的事件的时候。所以就可以通过updateComponentListeners方法把父组件事件监听器(比如click)传递给子组件。(这里不做过多讨论)
自定义事件的挂载方式
自定义事件的挂载是在eventsMixin方法中实现的。这里面将四个方法挂在Vue的原型上面。
Vue.prototype.$on
Vue.prototype.$once
Vue.prototype.$off
Vue.prototype.$emit
Vue.prototype.$on的实现
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 {
(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
}
第一个参数就是自定义事件,因为可能是数组,所以判断如果是数组,那么就循环调用this.$on方法。
如果不是数组,那么就直接向最开始定义的_events对象集合里面添加自定义事件。
所以这个时候_events对象生成的格式大概就是下面:
vm._events={
'test':[fn,fn...],
'test1':[fn,fn...]
}
Vue.prototype.$once 的实现
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
}
这里定义了一个on函数。接着把fn赋值给on.fn。最后在调用的是vm.$on。这里传入的就是事件名和前面定义的on函数。on函数在执行的时候会先移除_events中对应的事件,然后调用fn
所以分析下得到的是:
vm._events={
'oncetest':[
function on(){
vm.$off(event,on)
fn.apply(vm,arguments)
} ,
...
]
}
Vue.prototype.$off的实现
Vue.prototype.$off = function (event?: string | Array<string>, fn?: Function): Component {
const vm: Component = this
// all
// 如果没有传任何参数的时候,直接清楚所有挂在_events对象上的所有事件。
if (!arguments.length) {
vm._events = Object.create(null)
return vm
}
// array of events
// 如果第一个参数是数组的话,那么就循环调用this.$off方法
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]
// 没有相关的事件的时候直接返回vm实例
if (!cbs) {
return vm
}
// 如果只传入了事件名,那么清除该事件名下所有的事件。 也就是说 vm._events = {'test': null, ...}
if (!fn) {
vm._events[event] = null
return vm
}
// 如果传入的第二个参数为真,那么就去cbs里面遍历,在cbs中找到和fn相等的函数,然后通过splice删除该函数。
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
}
上面主要就是实现的下面三种情况:
- 如果没有提供参数,则移除所有的事件监听器;
- 如果只提供了事件,则移除该事件所有的监听器;
- 如果同时提供了事件与回调,则只移除这个回调的监听器。
Vue.prototype.$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}".`
)
}
}
// 匹配到事件列表,该列表是一个json。
let cbs = vm._events[event]
if (cbs) {
//将该json转化成为真正的数组
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
}
上面主要意思是:匹配到json中相关key值的value,这个value先转换成真正的数组,再循环遍历数组,传入给的参数执行数组中的每个函数
最后
vue中的自定义事件主要目的是为了组件之间的通信。因为_events对象是挂在Vue实例上的。因此每个组件是都可以访问到vm._events的值的,也能够向其中push值的。
整个自定义事件系统呢就是在vm实例上挂载一个_events的对象,可以理解为一个json,其中json的key值就是自定义事件的名称,一个key值可能对应着多个自定义事件,因此json中每个key对应的value都是一个数组,每次执行事件监听都会向数组中push相关的函数,最终通过$emit函数传入的参数,匹配到json中相应的key,val值,从而使用给定的参数执行数组中的函数。
最后的_events对象:
vm._events={
'test1':[fn,fn,fn],
'test2':[fn],
'oncetest':[
function on(){
vm.$off(event,on)
fn.apply(vm,arguments)
},
...
],
...
}
原文地址:https://segmentfault.com/a/1190000014838345
Vue实例方法之事件的实现的更多相关文章
- vue.js基础知识篇(5):过渡、Method和Vue实例方法
第8章:过渡 1.CSS过渡 2.JavaScript过渡 3.渐进过渡 第9章:method Vue.js的事件一般通过v-on指令配置在HTML中,虽然也可以在js的代码中使用原生的addEven ...
- vue for 绑定事件
vue for 绑定事件 <div id="pro_list" v-for="item in pro_list"> <div class=&q ...
- Vue方法与事件
gitHub地址:https://github.com/lily1010/vue_learn/tree/master/lesson10 一 vue方法实现 <!DOCTYPE html> ...
- React对比Vue(03 事件的对比,传递参数对比,事件对象,ref获取DOM节点,表单事件,键盘事件,约束非约束组件等)
import React from 'react'; class Baby extends React.Component { constructor (props) { super(props) t ...
- Vue中的事件与常见的问题处理
Vue的事件:获取事件对象$event: 事件冒泡:事件会向上传播 原生js阻止事件冒泡,需要先获取事件对象,再调用stopPropagation()方法: vue事件修饰符stop,例@clik.s ...
- vue教程1-05 事件 简写、事件对象、冒泡、默认行为、键盘事件
vue教程1-05 事件 简写.事件对象.冒泡.默认行为.键盘事件 v-on:click/mouseover...... 简写的: @click="" 推荐 事件对象: @clic ...
- vue教程1-04 事件 v-on:click="函数"
vue教程1-04 事件 v-on:click="函数" v-on:click/mouseout/mouseover/dblclick/mousedown..... 实例:为d ...
- [vue]vue v-on事件绑定(原生修饰符+vue自带事件修饰符)
preventDefault阻止默认行为和stopPropagation终止传递 event.preventDefault() 链接本来点了可以跳转, 如果注册preventDefault事件,则点了 ...
- Vue基础-自定义事件的表单输入组件、自定义组件的 v-model
Vue 测试版本:Vue.js v2.5.13 学习 Vue 的自定义事件的表单输入组件,觉得文档讲的不太细致,所以这里再细化一下: 如果不用 v-model,代码应该是这样: <myinput ...
随机推荐
- glance镜像服务
一.glance介绍: 因为云平台是提供Iass层的基础设施服务,我们拿到的是一台虚拟机,那么要用虚拟机的话必须有底层的镜像做支撑,所以说镜像的话也有一个服务来管理.但是我们云平台用的镜像不是装操作系 ...
- java基本数据类型所占字节数
JAVA基本数据类型所占字节数是多少?(32位系统) byte 1字节 short 2字节 int 4字节 ...
- poj 1258 Agri-Net prim模板 prim与dijkstra的区别
很裸地求最小生成树的题目.题意就不多说了,最重要的就是记录一下学会了prim算法. 初学prim,给我的第一感觉就是和dijkstra好像啊,感觉两者的区别还是有的: 1:prim是求最小生成树的算法 ...
- ArrayList、HashMap 与 员工类(程序员、经理的结合使用) 相当于集合与继承的总结
package Day28ketangzuoye; import java.util.ArrayList; import java.util.HashMap; import java.util.Map ...
- 205 Isomorphic Strings 同构字符串
给定两个字符串 s 和 t,判断它们是否是同构的.如果 s 中的字符可以被替换最终变成 t ,则两个字符串是同构的.所有出现的字符都必须用另一个字符替换,同时保留字符的顺序.两个字符不能映射到同一个字 ...
- [转]Monkey测试简介
转自:http://www.cnblogs.com/manuosex/p/3215270.html 在android手机上做自动化测试,monkey比cts,Android UnitTest 好用多了 ...
- 算法和数据结构~Sqlserver索引使用的B树
B树相关概念 在B-树中查找给定关键字的方法是,首先把根结点取来,在根结点所包含的关键字K1,…,Kn查找给定的关键字(可用顺序查找或二分查找法),若找到等于给定值的关键字,则查找成功:否则,一定可以 ...
- sdut1650I-Keyboard(dp)
链接 题目大意就相当于 跟你一串字符串 让你截成k段 使总体的值最小 想法是递归的 递归太慢 可以转换为递推的 这样就有可以推出状态方程 dp[i][j] = max(dp[i][j],dp[i-1] ...
- vue全局loading组件
本组件作用在页面加载完成前进行loader提示,提升用户体验,只需要在app.vue中引用一次,整个项目中路由切换时就可以自动进行提示(vuex版): 1. 添加vuex值和方法: import Vu ...
- windows server 2008 r2 IIS7下网站配置 只允许指定的IP地址访问
步骤一.找到ip地址和域限制 步骤二.添加全部拒绝 步骤三.添加允许访问的ip地址(局域网填写局域网ip,公网填写公网ip) 步骤四:如果想要拒绝某些ip访问,直接在规则中添加拒绝条目就可以