咱們在使用 vue
的時候,相信你必定也會對事件的處理比較感興趣。 咱們經過 @click
的時候,究竟是發生了什麼呢!javascript
雖然咱們用 @click
綁定在模板上,不過事件嚴格綁定在 vnode
上的 。html
eventlisteners
這個模塊,就是定義了一些鉤子,在 patch
的時候,可以進行事件的綁定以及解綁。vue
建議閱讀這個篇章以前,先閱讀 模塊 瞭解簡單的模塊以後,再回來
首先咱們看下暴露出來的內容:java
// 導出時間監聽模塊,建立、更新、銷燬 export const eventListenersModule = { create: updateEventListeners, update: updateEventListeners, destroy: updateEventListeners } as Module;
這裏咱們可以知道,在 create
、 update
、 destroy
的時候,便會觸發 ,調用 updateEventListeners
;node
接下來咱們來詳細瞭解下 updateEventListeners
;git
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); } } } } }
這裏咱們看到,事件觸發以後都會先回調到 listener ,那它是怎麼回調的呢。github
首先看下建立 listenersegmentfault
/** * 建立監聽器 */ function createListener() { // 事件處理器 return function handler(event: Event) { handleEvent(event, (handler as any).vnode); }; }
當事件觸發的時候,會調用 handleEvent(event, (handler as any).vnode);
數組
handleEvent
主要負責轉發 , 去除 on 裏面對應的事件處理函數,進行調用app
// 處理事件 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); } }
執行響應的事件處理程序。
主要是處理幾種狀況:
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 來做爲統一的事件接收, 更方便的對事件綁定以及解綁進行處理 ,在元素建立的時候綁定事件, 在銷燬的時候解綁事件,防止內存泄露。 這種解決方式也是至關優雅,值得學習 :)