深入理解jQuery的Event机制

jQuery的Event模块非常强大。其功能远远比原生事件监听器强大许多,对同一个元素的监听只用一个eventListener,内部则是一个强大的观察者,根据匹配事件类型触发相应回调。jQuery不仅封装了兼容性差异,还提供了命名空间式注册注销事件,灵活的事件委托(事件代理),手动触发事件trigger以及自定义事件。因为jQuery提供的bind,delegate,live(1.9版本废除了)的功能都是通过on来适配的,所以这里只讲on,off,trigger。

1.注册事件$.fn.on方法

on: function(types, selector, data, fn, /*INTERNAL*/ one) {
            var type, origFn;

            // 添加多个事件注册
            if (typeof types === "object") {
                // ( types-Object, selector, data )
                if (typeof selector !== "string") {
                    // ( types-Object, data )
                    data = data || selector;
                    selector = undefined;
                }
                // 为每个事件迭代
                for (type in types) {
                    this.on(type, selector, data, types[type], one);
                }
                return this;
            }

            // 如果data和fn都为空,则将selector赋值给fn,
            if (data == null && fn == null) {
                // ( types, fn )
                fn = selector;
                data = selector = undefined;
            } else if (fn == null) {
                if (typeof selector === "string") {
                    // ( types, selector, fn )
                    fn = data;
                    data = undefined;
                } else {
                    // ( types, data, fn )
                    fn = data;
                    data = selector;
                    selector = undefined;
                }
            }
            if (fn === false) {
                fn = returnFalse;
            } else if (!fn) {
                return this;
            }

            // 如果只是一次性事件,则将fn从新包装
            if (one === 1) {
                origFn = fn;
                fn = function(event) {
                    // 这里使用空的jq对象来解除事件绑定信息,
                    // 具体定位是通过event.handleObj和目标元素event.delegateTarget
                    jQuery().off(event);
                    // 执行原始的fn函数
                    return origFn.apply(this, arguments);
                };
                // Use same guid so caller can remove using origFn
                // 备忘信息
                fn.guid = origFn.guid || (origFn.guid = jQuery.guid++);
            }
            // 统一调用jQuery.event.add方法添加事件处理
            return this.each(function() {
                jQuery.event.add(this, types, fn, data, selector);
            });
        }

可以从源码看出前面都是针对其他高层api做的参数调整,最后都会调用jQuery.event.add这个方法来注册事件。

jQuery.event.add方法:

/**
         * 事件绑定最后都通过jQuery.event.add来实现。其执行过程大致如下:
         1.  先调用jQuery._data从$.cache中取出已有的事件缓存(私有数据,Cache的解析详见数据缓存)
         2.  如果是第一次在DOM元素上绑定该类型事件句柄,在DOM元素上绑定jQuery.event.handle,作为统一的事件响应入口
         3.  将封装后的事件句柄放入缓存中
         传入的事件句柄,会被封装到对象handleObj的handle属性上,此外handleObj还会填充guid、type、namespace、data属性;DOM事件句柄elemData.handle指向jQuery.event.handle,即jQuery在DOM元素上绑定事件时总是绑定同样的DOM事件句柄jQuery.event.handle。
         事件句柄在缓存$.cache中的数据结构如下,事件类型和事件句柄都存储在属性events中,属性handle存放的执行这些事件句柄的DOM事件句柄:
         elemData = {
    events: {
        'click' : [
            { guid: 5, type: 'click', namespace: '', data: undefined,
                handle: { guid: 5, prototype: {} }
            },
            { ... }
        ],
        'keypress' : [ ... ]
    },
    handle: { // DOM事件句柄
        elem: elem,
        prototype: {}
    }
}
         */
        add: function(elem, types, handler, data, selector) {
            var tmp, events, t, handleObjIn,
                special, eventHandle, handleObj,
                handlers, type, namespaces, origType,
                // 创建或获取私有的缓存数据
                elemData = jQuery._data(elem);

            if (!elemData) {
                return;
            }

            // 可以给jq的handler对象传参数配置
            if (handler.handler) {
                handleObjIn = handler;
                handler = handleObjIn.handler;
                selector = handleObjIn.selector;
            }

            // 确保处理程序有唯一ID,以便查找和删除
            // handler函数添加guid属性
            if (!handler.guid) {
                handler.guid = jQuery.guid++;
            }

            // 首次初始化元素的事件结构和主要处理程序
            // 缓存数据elemData添加events属性对象
            if (!(events = elemData.events)) {
                events = elemData.events = {};
            }
            // elemData添加handle方法
            if (!(eventHandle = elemData.handle)) {
                // 当我们使用jQuery为元素添加事件处理程序时,
                // 实际上就是调用了这个通过包装的函数,
                // 而这里面就是通过jQuery.event.dispatch方法来触发的
                eventHandle = elemData.handle = function(e) {
                    // 如果jQuery完成初始化且不存在e或者已经jQuery.event.trigger()了
                    // 返回派遣委托后的结果
                    // this指向eventHandle.elem,解决ie中注册事件this指向的问题
                    // 如果是IE,这里使用attachEvent监听,其事件处理程序的第一个参数就有ie的event了。
                    // 平时说的window.event是指在elem['on' + type] = handler;的情况
                    return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply(eventHandle.elem, arguments) :
                        undefined;
                };
                // 给handle函数添加elem属性防止IE非原生内存泄露
                // handle方法添加elem属性
                eventHandle.elem = elem;
            }

            // 处理空格分离的多事件
            // jQuery(...).bind("mouseover mouseout", fn);
            types = (types || '').match(core_rnotwhite) || [''];
            t = types.length;
            while (t--) {
                tmp = rtypenamespace.exec(types[t]) || [];
                type = origType = tmp[1];
                // 对命名空间进行排序
                // click.a.c.f.d --- a.c.d.f
                namespaces = (tmp[2] || '').split('.').sort();

                // 事件特例(就是为一些事件类型的一些特殊情况的处理)
                special = jQuery.event.special[type] || {};

                // 如果有事件特例,就使用。否则还是使用原始type
                type = (selector ? special.delegateType : special.bindType) || type;

                // 更新事件特例的类型
                special = jQuery.event.special[type] || {};

                // 给handleObj添加事件处理程序相关信息,
                // 如果target对象有相同属性或方法则替换为handleObj的
                handleObj = jQuery.extend({
                    type: type,
                    origType: origType,
                    data: data,
                    handler: handler,
                    guid: handler.guid,
                    selector: selector,
                    needsContext: selector && jQuery.expr.match.needsContext.test(selector),
                    namespace: namespaces.join('.')
                }, handleObjIn);

                // 首次初始化事件处理程序队列
                if (!(handlers = events[type])) {
                    handlers = events[type] = [];
                    handlers.delegateCount = 0;

                    // 当事件特例处理程序没有setup方法或者setup返回false时使用addEventListener/attachEvent
                    if (!special.setup || special.setup.call(elem, data, namespaces, eventHandle) === false) {
                        // 给元素绑定事件处理程序,知道这里才真正添加事件处理程序
                        if (elem.addEventListener) {
                            elem.addEventListener(type, eventHandle, false);
                        } else if (elem.attachEvent) {
                            elem.attachEvent('on' + type, eventHandle);
                        }
                    }
                }

                // 事件特例的一些处理
                if (special.add) {
                    special.add.call(elem, handleObj);

                    if (!handleObj.handler.guid) {
                        handleObj.handler.guid = handler.guid;
                    }
                }

                // 添加元素的事件处理列表,
                // 如果有selector,则用来给委托事件使用的
                if (selector) {
                    handlers.splice(handlers.delegateCount++, 0, handleObj);
                } else {
                    handlers.push(handleObj);
                }

                // 追踪哪个事件曾经被运行过
                jQuery.event.global[type] = true;
            }

            // 防止IE内存泄露
            elem = null;
        },

该方法会先从jQuery的缓存中查找该元素是否有事件缓存了,确保一个元素只需要一个原生的addEventListener/attachEvent。其实jQuery.event.add这个方法就是拼装元素事件所需数据,然后还存在缓存系统中,添加原生监听事件处理。在使用jQuery的方法注册事件的时候,我们来看一下$.cache中保存的对应的数据结构:

这是注册事件代码,注册了click,命名空间式的click.ns,dblclick以及自定义的事件custom:

$('#list').on('click', 'li', function(e){
        console.log(this);
        console.log(e);
    })
    .on('dblclick', function(e){
        console.log('dblclick');
    })
    .on('click.ns', function(e){
        console.log('ns.click');
    })
    .on('custom', function(e){
        console.log('custom');
    });

jQuery缓存系统中对应的事件处理器数据结构:

jQuery.event.add就是组装上面的数据结构

2是当前元素对应的缓存id,该对象下一级有两个对象events和handle,events保存着事件处理器,handle就是该元素对应的唯一一个原生事件监听处理程序的回调。

events里面保存着我们注册事件时的事件类型key对应的事件处理程序回调列表,回调列表里面的每一项保存着当前事件的信息,handler就是我们注册时的回调。我们还看到每个回调列表都会保存着一个delegateCount属性,这是jQuery计算出委托事件数目,如果用了委托注册,jQuery先会遍历你的事件类型,如果只有一个delegateCount就为1,否则就为对应的事件类型个数。delegateCount在后面触发事件时要用到。

既然已经注册好了,我们要蓄势待发了,接着是用户触发事件(用户行为的触发事件,非手动触发trigger),用户触发事件会触发原生的事件处理程序,然后进入到我们那个元素的对应唯一入口,处罚行为主要由jQuery.event.dispatch来完成:

/**
         * 派遣事件
         * 创建jQuery的event对象来代理访问原生的event,
         * 通过jQuery.event.handlers计算出委托事件处理队列handlerQueue(冒泡路径上的元素),没有委托则保存着当前元素和保存着其事件处理相关信息的对象handleObj。
         * 遍历委托事件处理队列,再遍历事件处理数组,找到匹配的事件类型,如果有处理程序,就执行它。可以使用event.stopImmediatePropagation()来阻止遍历下一个事件处理数组项。如果当前元素的当前事件处理程序返回值是false或者内部使用了event.stopPropagation()。就不会遍历下一个冒泡路径上的元素了(即当前元素的父级上的元素)
         * jQuery.event.special[event.type].preDispatch和jQuery.event.special[event.type].postDispatch分别是派遣事件开始和结束的钩子方法。
         * @param event 原生event对象
         * @returns {result|*}
         */
        dispatch: function(event) {
            // 从原生event中创建jq的event
            event = jQuery.event.fix(event);

            var i, ret, handleObj, matched, j,
                handlerQueue = [],
                args = core_slice.call(arguments),
                // 获取元素在jQuery.cache中的events对象的type数组
                handlers = (jQuery._data(this, 'events') || {})[event.type] || [],
                // 事件特例
                special = jQuery.event.special[event.type] || {};

            // 将第一个event参数替换为jq的event
            args[0] = event;
            // 设置委托目标
            event.delegateTarget = this;

            // 如果存在preDispatch钩子,则运行该方法后退出
            if (special.preDispatch && special.preDispatch.call(this, event) === false) {
                return;
            }

            // 委托事件队列
            handlerQueue = jQuery.event.handlers.call(this, event, handlers);

            // 先运行委托,如果阻止了冒泡就停止循环
            i = 0;
            while ((matched = handlerQueue[i++]) && !event.isPropagationStopped()) {
                event.currentTarget = matched.elem;

                j = 0;

                // 遍历当前元素的事件处理程序数组
                while ((handleObj = matched.handlers[j++]) && !event.isImmediatePropagationStopped()) {
                    // 被触发的时间不能有命名空间或者有命名空间,且被绑定的事件是命名空间的一个子集
                    if (!event.namespace_re || event.namespace_re.test(handleObj.namespace)) {
                        event.handleObj = handleObj;
                        event.data = handleObj.data;

                        // 尝试通过事件特例触发handle方法,如果没有则触发handleObj的handler方法
                        // mouseenter/mouseleave事件特例就是使用了该handle方法, 
                        // 事件特例的handle方法就是相当于一个装饰者,
                        // 把handleObj.handler包装了起来
                        ret = ((jQuery.event.special[handleObj.origType] || {}).handle || handleObj.handler).apply(matched.elem, args);

                        // 如果ret有值且是false则阻止默认行为和冒泡
                        // 即return false的时候阻止默认行为和冒泡
                        if (ret !== undefined) {
                            if ((event.result = ret) === false) {
                                event.preventDefault();
                                event.stopPropagation();
                            }
                        }
                    }
                }
            }

            // 运行postDispatch钩子方法
            if (special.postDispatch) {
                special.postDispatch.call(this, event);
            }

            return event.result;
        },
        // 处理委托事件的方法,返回一个队列,队列中每个元素有当前元素和匹配到的handler
        handlers: function(event, handlers) {
            var sel, handleObj, matches, i,
                handlerQueue = [],
                delegateCount = handlers.delegateCount,
                // 当前时间元素
                cur = event.target;

            // 是否有委托
            if (delegateCount && cur.nodeType && (!event.button || event.type !== 'click')) {
                // 遍历父辈元素,直到找到委托元素this
                for (; cur != this; cur = cur.parentNode || this) {
                    // 确保是元素且未禁用或者非点击事件
                    if (cur.nodeType === 1 && (cur.disabled !== true || event.type !== 'click')) {
                        matches = [];
                        // 遍历被委托事件处理程序,handlers[i]为jq的handler对象
                        for (i = 0; i < delegateCount; i++) {
                            handleObj = handlers[i];

                            // 当前handler的选择器字符, 加空格字符串是为了防止和Object.prototype属性冲突
                            sel = handleObj.selector + ' ';

                            // matches[sel]保存着当前元素是否在受委托元素中的标记
                            if (matches[sel] === undefined) {
                                matches[sel] = handleObj.needsContext ?
                                    jQuery(sel, this).index(cur) >= 0 :
                                    jQuery.find(sel, this, null, [cur]).length;
                            }
                            // 如果当前元素是在受委托元素中,则将当前handlerObj推入到matches数组中
                            if (matches[sel]) {
                                matches.push(handleObj);
                            }
                        }
                        // 如果matches数组有内容,则将新对象推入handlerQueue队列中
                        // elem保存着当前元素,handlers这保存着当前元素匹配的handlers
                        if (matches.length) {
                            handlerQueue.push({
                                elem: cur,
                                handlers: matches
                            });
                        }
                    }
                }
            }

            // 如果handlers还有剩余,把剩余的部分也推入到队列中
            if (delegateCount < handlers.length) {
                handlerQueue.push({
                    elem: this,
                    handlers: handlers.slice(delegateCount)
                });
            }

            return handlerQueue;
        },
        // 创建一个jq event对象,让其拥有和原始event一样的属性和值
        fix: function(event) {
            if (event[jQuery.expando]) {
                return event;
            }

            var i, prop, copy,
                type = event.type,
                originalEvent = event,
                fixHook = this.fixHooks[type];

            // 如果fixHook不存在判断是鼠标事件还是键盘事件再指向相应的钩子对象
            if (!fixHook) {
                this.fixHooks[type] = fixHook =
                    rmouseEvent.test(type) ? this.mouseHooks :
                    rkeyEvent.test(type) ? this.keyHooks : {};
            }
            // fixHook是否有props属性,该值是一个数组,如果有则添加到jQuery.event.props中
            copy = fixHook.props ? this.props.concat(fixHook.props) : this.props;
            // 创建一个jQuery Event实例event,默认行为和冒泡fix
            event = new jQuery.Event(originalEvent);

            // 给jq event添加原始event对象的属性
            i = copy.length;
            while (i--) {
                prop = copy[i];
                event[prop] = originalEvent[prop];
            }

            // Support: IE<9
            if (!event.target) {
                event.target = originalEvent.srcElement || document;
            }

            // Support: Chrome 23+, Safari?
            if (event.target.nodeType === 3) {
                event.target = event.target.parentNode;
            }

            // Support: IE<9
            event.metaKey = !! event.metaKey;

            // 如果钩子对象有filter解决兼容方法,则返回filter后的event
            return fixHook.filter ? fixHook.filter(event, originalEvent) : event;
        },

jQuery.event.dispatch的任务主要是:

首先通过jQuery.event.fix(event)创建jQuery的event对象来代理访问原生的event,jQuery.event.fix这个方法会对event做兼容处理。

然后通过jQuery.event.handlers计算出委托事件处理队列handlerQueue(冒泡路径上的元素),没有委托则保存着当前元素和保存着其事件处理相关信息的对象handleObj。遍历委托事件处理队列,再遍历事件处理数组,找到匹配的事件类型,如果有处理程序,就执行它。可以使用event.stopImmediatePropagation()来阻止遍历下一个事件处理数组项。如果当前元素的当前事件处理程序返回值是false或者内部使用了event.stopPropagation()。就不会遍历下一个冒泡路径上的元素了(即当前元素的父级上的元素)。

jQuery.event.special[event.type].preDispatch和jQuery.event.special[event.type].postDispatch分别是派遣事件开始和结束的钩子方法

 

然后是手动触发事件$.fn.trigger:

/**
         * 1.可触发自定义事件
         * 2.触发原生事件处理程序
         * 1).通过jQuery定义的
         * 2).如果触发该类型事件都会触发elem[type]和elem['on' + type]方法,如果没有冒泡阻止,也会触发其他冒泡路径上的元素的ontype方法
         *
         * @param event
         * @param data
         * @param elem
         * @param onlyHandlers
         * @returns {*}
         */
        trigger: function(event, data, elem, onlyHandlers) {
            var handle, ontype, cur,
                bubbleType, special, tmp, i,
                eventPath = [elem || document],
                type = core_hasOwn.call(event, 'type') ? event.type : event,
                namespaces = core_hasOwn.call(event, 'namespace') ? event.namespace.split('.') : [];

            cur = tmp = elem = elem || document;

            if (elem.nodeType === 3 || elem.nodeType === 8) {
                return;
            }

            // focus/blur变形为focusin/out,确保我们不会立刻触发它们
            if (rfocusMorph.test(type + jQuery.event.triggered)) {
                return;
            }

            if (type.indexOf('.') >= 0) {
                namespaces = type.split('.');
                // 取出第一项,事件类型
                type = namespaces.shift();
                // 命名空间排序
                namespaces.sort();
            }
            ontype = type.indexOf(':') < 0 && 'on' + type;

            // 确保是jQuery的event对象
            event = event[jQuery.expando] ?
                event :
                new jQuery.Event(type, typeof event === 'object' && event);

            event.isTrigger = true;
            event.namespace = namespaces.join('.');
            event.namespace_re = event.namespace ?
                new RegExp('(^|\\.)' + namespaces.join('\\.(?:.*\\.|)') + '(\\.|$)') :
                null;

            // 清除事件,防止被重用
            event.result = undefined;
            if (!event.target) {
                event.target = elem;
            }

            // 克隆来源数据和预先准备事件,创建处理程序参数列表
            data = data == null ?
                [event] :
                jQuery.makeArray(data, [event]);

            // 特殊的情况下的trigger
            special = jQuery.event.special[type] || {};
            if (!onlyHandlers && special.trigger && special.trigger.apply(elem, data) === false) {
                return;
            }

            // 保存冒泡时经过的元素到eventPath中,向上冒到document,然后到window;也可能是全局ownerDocument变量
            if (!onlyHandlers && !special.noBubble && !jQuery.isWindow(elem)) {
                bubbleType = special.delegateType || type;
                if (!rfocusMorph.test(bubbleType + type)) {
                    // 如果不是focus/blur类型,将当前元素改为父节点元素
                    cur = cur.parentNode;
                }
                // 一直向上获取父辈元素并存入eventPath数组中
                for (; cur; cur = cur.parentNode) {
                    eventPath.push(cur);
                    tmp = cur;
                }

                // 如tmp到了document,我们添加window对象
                if (tmp === (elem.ownerDocument || document)) {
                    eventPath.push(tmp.defaultView || tmp.parentWindow || window);
                }
            }

            // 在事件路径上触发处理程序, 如果没有阻止冒泡就会遍历eventPath,
            // 如果当前元素对应的事件类型有事件处理程序,就执行它,直到到最顶元素。
            // 如果阻止,在第一次遍历后就不会再遍历了。
            i = 0;
            while ((cur = eventPath[i++]) && !event.isPropagationStopped()) {
                event.type = i > 1 ?
                    bubbleType :
                    special.bindType || type;

                // jQuery 缓存中的处理程序
                handle = (jQuery._data(cur, 'events') || {})[event.type] && jQuery._data(cur, 'handle');
                // 如果有handle方法,执行它。这里的handle是元素绑定的事件
                if (handle) {
                    handle.apply(cur, data);
                }

                // 触发原生处理程序
                handle = ontype && cur[ontype];
                if (handle && jQuery.acceptData(cur) && handle.apply && handle.apply(cur, data) === false) {
                    event.preventDefault();
                }
            }
            event.type = type;

            // 如果没有阻止默认行为动作,处理elem的type属性事件,
            // 执行elem[type]处理程序但不会触发elem['on' + type]
            if (!onlyHandlers && !event.isDefaultPrevented()) {
                // 1.
                // 1).没有special._default
                // 2).有special._default,该方法的执行结果返回false
                // 2.
                // type不能使click且elem不能使a标签
                // 3.
                // elem可接受缓存
                if ((!special._default || special._default.apply(elem.ownerDocument, data) === false) && !(type === 'click' && jQuery.nodeName(elem, 'a')) && jQuery.acceptData(elem)) {

                    if (ontype && elem[type] && !jQuery.isWindow(elem)) {
                        // 缓存older
                        tmp = elem[ontype];

                        // 当我们执行foo()时,不会重新触发onfoo事件
                        if (tmp) {
                            elem[ontype] = null;
                        }

                        // 防止再次触发中的相同事件,第一次触发完后jQuery.event.triggered = undefined
                        jQuery.event.triggered = type;
                        try {
                            // 执行方法
                            elem[type]();
                        } catch (e) {
                            // 隐藏元素在focus/blur时,ie9以下会奔溃
                        }
                        jQuery.event.triggered = undefined;

                        if (tmp) {
                            elem[ontype] = tmp;
                        }
                    }
                }
            }

            return event.result;
        },

trigger会创建一个新的jQuery Event对象,添加一些trigger的附加属性,onlyHandlers和!onlyHandlers参数代表triggerHandler和trigger的区别。

1.trigger会先采集冒泡路径上的元素保存到eventPath数组中,

2.在没有阻止冒泡的情况下,然后遍历eventPath,找到对应的我们注册的事件处理程序,这里分两种事件处理,jQuery方式添加的还有原生elem['on' + type]形式添加的,这个过程都会触发前面两种事件处理程序。

3.在没有阻止默认行为的情况下,然后就是执行当前元素的elem[type]方式的事件处理程序,这种方式的事件处理程序是通过调用原生的事件注册addEventListener/attachEvent(所以如果没阻止冒泡,它就会向上冒泡了),当然这个步骤要避免触发上一步的事件程序,即jQuery的原生注册接口和ontype形式的,通过jQuery.event.triggered来保存已经被触发了的标志,这样jQuery的原生注册接口通过判断jQuery.event.triggered来决定是否触发。而ontype形式的就先把ontype至为null,执行完操作后再恢复。

而triggerHandler就只做了trigger过程中的第二步,只是eventPath之保存了一个元素,就是当前元素.

最后是注销事件$.fn.off

off: function(types, selector, fn) {
            var handleObj, type;
            // 当传递的types是jQuery创建的event对象时
            if (types && types.preventDefault && types.handleObj) {
                // ( event )  dispatched jQuery.Event
                handleObj = types.handleObj;
                jQuery(types.delegateTarget).off(
                    handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
                    handleObj.selector,
                    handleObj.handler
                );
                return this;
            }
            // 当types是对象,遍历递归
            if (typeof types === "object") {
                // ( types-object [, selector] )
                for (type in types) {
                    this.off(type, selector, types[type]);
                }
                return this;
            }
            if (selector === false || typeof selector === "function") {
                // ( types [, fn] )
                fn = selector;
                selector = undefined;
            }
            if (fn === false) {
                fn = returnFalse;
            }
            // 统一调用jQuery.event.remove移除事件处理程序及相关信息
            return this.each(function() {
                jQuery.event.remove(this, types, fn, selector);
            });
        },

$.fn.off实际上调用的是jQuery.event.remove这个方法:

/**
         * 注销元素的事件或者事件集
         * 
         * 通过jQuery.event.remove实现,其执行过程大致如下:
         1. 现调用jQuery._data从缓存$.cache中取出elem对应的所有数组(内部数据,与调用jQuery.data存储的数据稍有不同
         2. 如果未传入types则移除所有事件句柄,如果types是命名空间,则移除所有与命名空间匹配的事件句柄
         3. 如果是多个事件,则分割后遍历
         4. 如果未指定删除哪个事件句柄,则删除事件类型对应的全部句柄,或者与命名空间匹配的全部句柄
         5. 如果指定了删除某个事件句柄,则删除指定的事件句柄
         6. 所有的事件句柄删除,都直接在事件句柄数组jQuery._data( elem ).events[ type ]上调用splice操作
         7. 最后检查事件句柄数组的长度,如果为0,或为1但要删除,则移除绑定在elem上DOM事件
         8. 最后的最后,如果elem对应的所有事件句柄events都已删除,则从缓存中移走elem的内部数据
         9. 在以上的各个过程,都要检查是否有特例需要处理
         */
        remove: function(elem, types, handler, selector, mappedTypes) {
            var j, handleObj, tmp,
                origCount, t, events,
                special, handlers, type,
                namespaces, origType,
                elemData = jQuery.hasData(elem) && jQuery._data(elem);

            if (!elemData || !(events = elemData.events)) {
                return;
            }

            types = (types || '').match(core_rnotwhite) || [''];
            t = types.length;
            while (t--) {
                tmp = rtypenamespace.exec(types[t]) || [];
                type = origType = tmp[1];
                namespaces = (tmp[2] || '').split('.').sort();

                // 如果没有指定type,解绑元素的所有事件(包括命名空间上的)
                if (!type) {
                    for (type in events) {
                        jQuery.event.remove(elem, type + types[t], handler, selector, true);
                    }
                    continue;
                }

                special = jQuery.event.special[type] || {};
                type = (selector ? special.delegateType : special.bindType) || type;
                // 该事件列表
                handlers = events[type] || [];
                tmp = tmp[2] && new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)");

                // 删除匹配的事件

                // 事件列表的长度
                origCount = j = handlers.length;
                while (j--) {
                    handleObj = handlers[j];

                    if ((mappedTypes || origType === handleObj.origType) &&
                        (!handler || handler.guid === handleObj.guid) &&
                        (!tmp || tmp.test(handleObj.namespace)) &&
                        (!selector || selector === handleObj.selector || selector === "**" && handleObj.selector)) {
                        // 删除events事件列表中的该项
                        handlers.splice(j, 1);
                        // 如果有委托,delegateCount就减一
                        if (handleObj.selector) {
                            handlers.delegateCount--;
                        }
                        if (special.remove) {
                            special.remove.call(elem, handleObj);
                        }
                    }
                }

                // 删除通用的事件处理程序,同时避免无限递归

                // 如果原始事件列表有项,经过前面的步骤长度为0
                if (origCount && !handlers.length) {
                    if (!special.teardown || special.teardown.call(elem, namespaces, elemData.handle) === false) {
                        // 删除注册的侦听事件
                        jQuery.removeEvent(elem, type, elemData.handle);
                    }

                    // 删除events[type]属性
                    delete events[type];
                }
            }

            // 如果events不再使用则删除
            if (jQuery.isEmptyObject(events)) {
                delete elemData.handle;

                // 使用removeData检查空的和清空expando
                jQuery._removeData(elem, 'events');
            }
        },

注销嘛,就是对我们保存在缓存系统中的对应数据进行销毁,这里不赘述了。

总结:

jQuery的event模块非常的强大,我也只是讲了一般流程,它还有一些钩子对象处理浏览器兼容问题,我这里就不探讨了。希望我的讲解可以令你解惑。

转载于:https://my.oschina.net/feanlau/blog/968245

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值