本文將解析vue實例上與事件派發相關的4個方法,分別爲:vue
首先整體介紹下這四個方法實現的基本原理:數組
一、vue實例上會建立一個對象來保存全部要監聽的事件:vm._events = {}app
二、每當咱們要監聽一個事件,就往vm._events裏添加一個鍵值對,事件的名稱做爲鍵,一個空數組做爲值。例如咱們要監聽的事件名稱爲event1
,則vm._events = {event1: []}
函數
三、監聽事件的回調函數都會添加到對應的數組中,例如咱們調用ui
vm.$on('event1', cb1);
vm.$on('event1', cb2);
vm.$on('event1', cb3);
複製代碼
則此時vm._events={event1: [cb1, cb2, cb3]}
this
四、當調用移除監聽事件的方法時,所作的操做爲移除其對應數組裏的回調函數,例如當咱們調用spa
vm.$off('event1', cb1);
複製代碼
這時cb1就被移除了,vm._events={event1: [cb2, cb3]}
prototype
五、當咱們執行$emit
觸發對應事件時,所作的操做就是把該事件對應數組裏的回調函數都拿出來執行一遍,例如當咱們調用code
vm.$emit('event')
複製代碼
這時候會取出event1
對應數組裏的cb2
和cb3
執行對象
六、$once
表示該事件只會觸發執行一次,後面在觸發就沒用了,例如當咱們調用:
// 用$on方法監聽event2,回調函數爲cb4
vm.$on('event2', cb4);
// 用$once方法監聽event2,回調函數爲cb5
vm.$once('event2', cb5);
// 觸發event2事件,會執行cb4和cb5
vm.$emit('event2');
// 再次觸發event2事件,這裏只會執行cb5,不會執行cb4,cb4只會執行一次
vm.$emit('event2');
複製代碼
以上即爲事件派發方法的基本原理,固然這些方法還有一些稍微複雜一點的使用方式,好比
看完下面的源碼解析就知道這些狀況都是怎麼處理的了
下面來看下vue源碼裏這四個方法的具體實現:
Vue.prototype.$on = function (event, fn) {
const vm = this
// 咱們傳入的要監聽的事件可能爲數組,這時候對數組裏的每一個事件再遞歸調用$on方法
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$on(event[i], fn)
}
} else {
// 以前已經有監聽event事件,則將這次監聽的回調函數添加到其數組中,不然建立一個新數組並添加fn
(vm._events[event] || (vm._events[event] = [])).push(fn)
}
return vm
}
複製代碼
Vue.prototype.$off = function (event, fn) {
const vm = this
// all
if (!arguments.length) {
// 若是沒有傳參數,則清空全部事件的監聽函數
vm._events = Object.create(null)
return vm
}
// 若是傳的event是數組,則對該數組裏的每一個事件再遞歸調用$off方法
if (Array.isArray(event)) {
for (let i = 0, l = event.length; i < l; i++) {
vm.$off(event[i], fn)
}
return vm
}
// 獲取當前event裏全部的回調函數
const cbs = vm._events[event]
// 若是不存在回調函數,則直接返回,由於沒有能夠移除監聽的內容
if (!cbs) {
return vm
}
// 若是沒有指定要移除的回調函數,則移除該事件下全部的回調函數
if (!fn) {
vm._events[event] = null
return vm
}
// 指定了要移除的回調函數
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 = function (event) {
const vm = this
// 拿出觸發事件對應的回調函數列表
let cbs = vm._events[event]
if (cbs) {
// $emit方法能夠傳參,這些參數會在調用回調函數的時候傳進去
const args = toArray(arguments, 1)
// 遍歷回調函數列表,調用每一個回調函數
for (let i = 0, l = cbs.length; i < l; i++) {
cbs[i].apply(vm, args)
}
}
return vm
}
}
複製代碼
Vue.prototype.$once = function (event, fn) {
const vm = this
// 封裝一個高階函數on,在on裏面調用fn
function on () {
// 每當執行了一次on,移除event裏的on事件,後面再觸發event事件就不會再執行on事件了,也就不會執行on裏面的fn事件
vm.$off(event, on)
// 執行on的時候,執行fn函數
fn.apply(vm, arguments)
}
// 這個賦值是在$off方法裏會用到的
// 好比咱們調用了vm.$off(fn)來移除fn回調函數,然而咱們在調用$once的時候,實際執行的是vm.$on(event, on)
// 因此在event的回調函數數組裏添加的是on函數,這個時候要移除fn,咱們沒法在回調函數數組裏面找到fn函數移除,只能找到on函數
// 咱們能夠經過on.fn === fn來判斷這種狀況,並在回調函數數組裏移除on函數
on.fn = fn
// $once最終調用的是$on,而且回調函數是on
vm.$on(event, on)
return vm
}
複製代碼