snabbdom源碼解析(七) 事件處理

事件處理

我們在使用 vue 的時候,相信你一定也會對事件的處理比較感興趣。 我們通過 @click 的時候,到底是發生了什麼呢!

雖然我們用 @click綁定在模板上,不過事件嚴格綁定在 vnode 上的 。

eventlisteners 這個模塊,就是定義了一些鉤子,在 patch 的時候,能夠進行事件的綁定以及解綁。

建議閱讀這個篇章之前,先閱讀 模塊 瞭解簡單的模塊之後,再回來

eventlisteners 模塊

首先我們看下暴露出來的內容:

// 導出時間監聽模塊,創建、更新、銷燬
export const eventListenersModule = {
    create: updateEventListeners,
    update: updateEventListeners,
    destroy: updateEventListeners
} as Module;

這裏我們能夠知道,在 createupdatedestroy 的時候,便會觸發 ,調用 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

執行響應的事件處理程序。

主要是處理幾種情況:

  1. handler 爲函數的情況
  2. handlerobject , 但是第一個元素爲 function 的情況 ,eg: handler = [fn,arg1,arg2] ;
  3. handlerobject ,第一個元素不爲 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 來作爲統一的事件接收, 更方便的對事件綁定以及解綁進行處理 ,在元素創建的時候綁定事件, 在銷燬的時候解綁事件,防止內存泄露。 這種解決方式也是相當優雅,值得學習 :)

snabbdom源碼解析系列

snabbdom源碼解析(一) 準備工作

snabbdom源碼解析(二) h函數

snabbdom源碼解析(三) vnode對象

snabbdom源碼解析(四) patch 方法

snabbdom源碼解析(五) 鉤子

snabbdom源碼解析(六) 模塊

snabbdom源碼解析(七) 事件處理

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章