资讯专栏INFORMATION COLUMN

snabbdom源码解析(七) 事件处理

Kross / 516人阅读

摘要:这种解决方式也是相当优雅,值得学习源码解析系列源码解析一准备工作源码解析二函数源码解析三对象源码解析四方法源码解析五钩子源码解析六模块源码解析七事件处理个人博客地址

事件处理

我们在使用 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

执行响应的事件处理程序。

主要是处理几种情况:

handler 为函数的情况

handlerobject , 但是第一个元素为 function 的情况 ,eg: handler = [fn,arg1,arg2] ;

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源码解析(七) 事件处理

个人博客地址

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100418.html

相关文章

  • snabbdom源码解析(三) vnode对象

    摘要:对象是一个对象,用来表示相应的结构代码位置定义类型定义类型选择器数据,主要包括属性样式数据绑定时间等子节点关联的原生节点文本唯一值,为了优化性能定义的类型定义绑定的数据类型属性能直接用访问的属性样式类样式数据绑定的事件钩子创建对象根据传入的 vnode 对象 vnode 是一个对象,用来表示相应的 dom 结构 代码位置 :./src/vnode.ts 定义 vnode 类型 /** ...

    willin 评论0 收藏0
  • snabbdom源码解析(二) h函数

    介绍 这里是 typescript 的语法,定义了一系列的重载方法。h 函数主要根据传进来的参数,返回一个 vnode 对象 代码 代码位置 : ./src/h.ts /** * 根据选择器 ,数据 ,创建 vnode */ export function h(sel: string): VNode; export function h(sel: string, data: VNodeData...

    Jensen 评论0 收藏0
  • snabbdom源码解析(五) 钩子

    摘要:元素从父节点删除时触发,和略有不同,只影响到被移除节点中最顶层的节点在方法的最后调用,也就是完成后触发源码解析系列源码解析一准备工作源码解析二函数源码解析三对象源码解析四方法源码解析五钩子源码解析六模块源码解析七事件处理个人博客地址 文件路径 : ./src/hooks.ts 这个文件主要是定义了 Virtual Dom 在实现过程中,在其执行过程中的一系列钩子。方便外部做一些处理 /...

    Worktile 评论0 收藏0
  • snabbdom源码解析(一) 准备工作

    摘要:阅读源码的时候,想了解虚拟结构的实现,发现在的地方。然而慢慢的人们发现,在我们的代码中布满了一系列操作的代码。源码解析系列源码解析一准备工作源码解析二函数源码解析三对象源码解析四方法源码解析五钩子源码解析六模块源码解析七事件处理个人博客地址 前言 虚拟 DOM 结构概念随着 react 的诞生而火起来,之后 vue2.0 也加入了虚拟 DOM 的概念。 阅读 vue 源码的时候,想了解...

    defcon 评论0 收藏0
  • snabbdom源码解析(六) 模块

    摘要:模块在里面,定义了一系列的模块,这些模块定义了相应的钩子。主要接受两个参数,。传送门事件模块待续。。。源码解析系列源码解析一准备工作源码解析二函数源码解析三对象源码解析四方法源码解析五钩子源码解析六模块源码解析七事件处理个人博客地址 模块 在 ./src/modules 里面,定义了一系列的模块 , 这些模块定义了相应的钩子 。这些钩子会在 patch 的不同阶段触发,以完成相应模块的...

    zone 评论0 收藏0

发表评论

0条评论

Kross

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<