事件處理
我們在使用 vue
的時候,相信你一定也會對事件的處理比較感興趣。 我們通過 @click
的時候,到底是發生了什麼呢!
雖然我們用 @click
綁定在模板上,不過事件嚴格綁定在 vnode
上的 。
eventlisteners
這個模塊,就是定義了一些鉤子,在 patch
的時候,能夠進行事件的綁定以及解綁。
建議閱讀這個篇章之前,先閱讀 模塊 瞭解簡單的模塊之後,再回來
eventlisteners 模塊
首先我們看下暴露出來的內容:
// 導出時間監聽模塊,創建、更新、銷燬
export const eventListenersModule = {
create: updateEventListeners,
update: updateEventListeners,
destroy: updateEventListeners
} as Module;
這裏我們能夠知道,在 create
、 update
、 destroy
的時候,便會觸發 ,調用 updateEventListeners
;
接下來我們來詳細瞭解下 updateEventListeners
;
updateEventListeners
閱讀之前加兩個小的知識點,有助於理解
-
vnode.data.on
: 這個保存了一系列的綁定事件。 例如 on['click'] ,裏面保存了綁定的 click 事件 -
vnode.listener
: 作爲實際綁定到元素上的回調 。elm.addEventListener(name, listener, false);
。所有的事件觸發後都是先回調到listener
,再分發給不同的事件處理器
updateEventListeners
函數的主要邏輯如下 :
- 刪除新事件列表上不存在的事件
- 添加新增的事件
/**
* 更新事件監聽器
*/
function updateEventListeners(oldVnode: VNode, vnode?: VNode): void {
var oldOn = (oldVnode.data as VNodeData).on,
oldListener = (oldVnode as any).listener,
oldElm: Element = oldVnode.elm as Element,
on = vnode && (vnode.data as VNodeData).on,
elm: Element = (vnode && vnode.elm) as Element,
name: string;
// optimization for reused immutable handlers
if (oldOn === on) {
return;
}
// remove existing listeners which no longer used
// 刪除多餘的事件
if (oldOn && oldListener) {
// if element changed or deleted we remove all existing listeners unconditionally
if (!on) {
// 如果新的節點沒有綁定事件,則刪除所有的事件
for (name in oldOn) {
// remove listener if element was changed or existing listeners removed
// 刪除監聽器
oldElm.removeEventListener(name, oldListener, false);
}
} else {
for (name in oldOn) {
// remove listener if existing listener removed
// 刪除在新事件列表上不存在的監聽器
if (!on[name]) {
oldElm.removeEventListener(name, oldListener, false);
}
}
}
}
// add new listeners which has not already attached
if (on) {
// reuse existing listener or create new
// 重用老的監聽器
var listener = ((vnode as any).listener =
(oldVnode as any).listener || createListener());
// update vnode for listener
listener.vnode = vnode;
// if element changed or added we add all needed listeners unconditionally
if (!oldOn) {
for (name in on) {
// add listener if element was changed or new listeners added
elm.addEventListener(name, listener, false);
}
} else {
for (name in on) {
// add listener if new listener added
// 添加新增的監聽器
if (!oldOn[name]) {
elm.addEventListener(name, listener, false);
}
}
}
}
}
createListener
這裏我們看到,事件觸發之後都會先回調到 listener ,那它是怎麼回調的呢。
首先看下創建 listener
/**
* 創建監聽器
*/
function createListener() {
// 事件處理器
return function handler(event: Event) {
handleEvent(event, (handler as any).vnode);
};
}
handleEvent
當事件觸發的時候,會調用 handleEvent(event, (handler as any).vnode);
handleEvent
主要負責轉發 , 去除 on 裏面對應的事件處理函數,進行調用
// 處理事件
function handleEvent(event: Event, vnode: VNode) {
var name = event.type,
on = (vnode.data as VNodeData).on;
// call event handler(s) if exists
// 如果存在回調函數,則調用對應的函數
if (on && on[name]) {
invokeHandler(on[name], vnode, event);
}
}
invokeHandler
執行響應的事件處理程序。
主要是處理幾種情況:
-
handler
爲函數的情況 -
handler
爲object
, 但是第一個元素爲function
的情況 ,eg:handler = [fn,arg1,arg2]
; -
handler
爲object
,第一個元素不爲function
的情況 , eg:handler = [[fn1,arg1],[fn2]]
/**
* 調用事件處理
*/
function invokeHandler(handler: any, vnode?: VNode, event?: Event): void {
if (typeof handler === 'function') {
// call function handler
// 函數情況下直接調用
handler.call(vnode, event, vnode);
} else if (typeof handler === 'object') {
// call handler with arguments
if (typeof handler[0] === 'function') {
// handler爲數組的情況。 eg : handler = [fn,arg1,arg2]
// 第一項爲函數說明後面的項爲想要傳的參數
// special case for single argument for performance
if (handler.length === 2) {
// 當長度爲2的時候,用call,優化性能
handler[0].call(vnode, handler[1], event, vnode);
} else {
// 組裝參數,用 apply 調用
var args = handler.slice(1);
args.push(event);
args.push(vnode);
handler[0].apply(vnode, args);
}
} else {
// call multiple handlers
// 處理多個handler的情況
for (var i = 0; i < handler.length; i++) {
invokeHandler(handler[i]);
}
}
}
}
小結
這裏通過 listener 來作爲統一的事件接收, 更方便的對事件綁定以及解綁進行處理 ,在元素創建的時候綁定事件, 在銷燬的時候解綁事件,防止內存泄露。 這種解決方式也是相當優雅,值得學習 :)