jQuery event事件

jQuery.fn.on

在选择元素上绑定一个或多个事件的事件处理函数。

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

    // types可以是一个由types/handlers组成的map对象
    if ( typeof types === "object" ) {
        // 如果selector不是字符串
        // 则将传参由( types-Object, selector, data )变成( types-Object, data )
        if ( typeof selector !== "string" ) {
            data = data || selector;
            selector = undefined;
        }
        //遍历所有type
        for ( type in types ) {
            //添加type事件处理函数
            this.on( type, selector, data, types[ type ], one );
        }
        return this;
    }

    // 如果data为空,且fn为空
    if ( data == null && fn == null ) {
        // 则传参由( types, selector )变成( types, fn )
        fn = selector;
        data = selector = undefined;
    // 否则如果只是fn为空
    } else if ( fn == null ) {
        // 如果selector为字符串
        if ( typeof selector === "string" ) {
            // 则传参从( types, selector, data )变成( types, selector, fn )
            fn = data;
            data = undefined;
        } else {
            // 否则传参从( type, selector, data )变成( types, data, fn )
            fn = data;
            data = selector;
            selector = undefined;
        }
    }
    
    //……弄了半天其实就是在模拟重载而已……囧rz
    
    if ( fn === false ) {
        //如果fn为false则变成一个return false的函数
        fn = returnFalse;
    } else if ( !fn ) {
        //如果fn现在还不存在,则直接return this
        return this;
    }

    // 如果one为1
    if ( one === 1 ) {
        // 保存fn
        origFn = fn;
        // 重新定义fn
        fn = function( event ) {
            // 这个事件只用一次,用完就用off取消掉。
            jQuery().off( event );
            return origFn.apply( this, arguments );
        }
        // 使用相同的ID,为了未来好删除事件
        fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
    }
    // 对所有用jQuery.event.add来添加
    return this.each( function() {
        jQuery.event.add( this, types, fn, data, selector );
    });
};

文档中对selector的描述是:

  一个选择器字符串用于过滤器的触发事件的选择器元素的后代。如果选择的< null或省略,当它到达选定的元素,事件总是触发。

  A selector string to filter the descendants of the selected elements that trigger the event. If the selector isnull or omitted, the event is always triggered when it reaches the selected element.

说得貌似很悬乎,不过其实主要是在delegate绑定事件中去过滤元素的一些用不着的后代的。

jQuery.event.add

jQuery.event.add = function( elem, types, handler, data, selector ) {

    var handleObjIn, eventHandle, tmp,
        events, t, handleObj,
        special, handlers, type, namespaces, origType,
        // 通过内部缓存获取元素数据
        elemData = jQuery._data( elem );

    // 不会没有数据或者text、comment节点添加事件
    if ( !elemData ) {
        return;
    }

    // 如果handler是个包含handler和selector的对象
    if ( handler.handler ) {
        // 则定位必要的参数
        handleObjIn = handler;
        handler = handleObjIn.handler;
        selector = handleObjIn.selector;
    }

    // 如果handler没有ID,则给个ID给他
    // 用于未来寻找或者删除handler
    if ( !handler.guid ) {
        handler.guid = jQuery.guid++;
    }

    // 如果缓存数据中没有events数据
    if ( !(events = elemData.events) ) {
        // 则初始化events
        events = elemData.events = {};
    }
    // 如果缓存数据中没有handle数据
    if ( !(eventHandle = elemData.handle) ) {
        // 定义事件处理函数
        eventHandle = elemData.handle = function( e ) {
            // 取消jQuery.event.trigger第二次触发事件
            // 以及装卸后的事件
            return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
                jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
                undefined;
        };
        // 定义事件处理器对应的元素,用于防止IE非原生事件中的内存泄露
        eventHandle.elem = elem;
    }

    // 事件可能是通过空格键分隔的字符串,所以将其变成字符串数组
    types = ( types || "" ).match( core_rnotwhite ) || [""];
    // 事件的长度
    t = types.length;
    // 遍历所有事件
    while ( t-- ) {
        // 尝试取出事件的namespace,如aaa.bbb.ccc
        tmp = rtypenamespace.exec( types[t] ) || [];
        // 取出事件,如aaa
        type = origType = tmp[1];
        // 取出事件命名空间,如bbb.ccc,并根据"."分隔成数组
        namespaces = ( tmp[2] || "" ).split( "." ).sort();

        // 事件是否会改变当前状态,如果会则使用特殊事件
        special = jQuery.event.special[ type ] || {};

        // 根据是否已定义selector,决定使用哪个特殊事件api,如果没有非特殊事件,则用type
        type = ( selector ? special.delegateType : special.bindType ) || type;

        // 更具状态改变后的特殊事件
        special = jQuery.event.special[ type ] || {};

        // 组装用于特殊事件处理的对象
        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;

            // 如果获取特殊事件监听方法失败,则使用addEventListener进行添加事件,和attachEvent说88了
            if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
                if ( elem.addEventListener ) {
                    elem.addEventListener( type, eventHandle, false );
                }
            }
        }

        // 通过特殊事件add处理事件
        if ( special.add ) {
            // 添加事件
            special.add.call( elem, handleObj );

            // 设置处理函数的ID
            if ( !handleObj.handler.guid ) {
                handleObj.handler.guid = handler.guid;
            }
        }

        // 将事件处理函数推入处理列表
        if ( selector ) {
            handlers.splice( handlers.delegateCount++, 0, handleObj );
        } else {
            handlers.push( handleObj );
        }

        // 表示事件曾经使用过,用于事件优化
        jQuery.event.global[ type ] = true;
    }

    // 设置为null避免IE中循环引用导致的内存泄露
    elem = null;
};

从这里的源代码看,

  • 对于没有特殊事件特有监听方法和普通事件都用addEventListener来添加事件了。
  • 而又特有监听方法的特殊事件,则用了另一种方式来添加事件。

special.add从2.0的源代码来看,似乎没用用到,看起来是遗留问题,未来也可以根据这个扩展事件模型。

jQuery.event.dispatch

我们先走第一个分支,也就是通过addEventListener触发jQuery.event.dispatch。

jQuery.event.dispatch = function( event ) {

    // 重写原生事件对象,变成一个可读写的对象,方便未来修改、扩展
    event = jQuery.event.fix( event );

    var i, j, ret, matched, handleObj,
        handlerQueue = [],
        // 把参数转成数组
        args = core_slice.call( arguments ),
        // 从内部数据中查找该元素的对应事件处理器列表中的对应处理器,否则为空数组
        handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
        // 尝试将事件转成特殊事件
        special = jQuery.event.special[ event.type ] || {};

    // 将参数数组第一个元素换成重写的事件对象
    args[0] = event;
    event.delegateTarget = this;
    
    // 尝试使用特殊事件的preDispatch钩子来绑定事件,并在必要时退出
    if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
        return;
    }

    // 组装事件处理包{elem, handlerObjs}(这里是各种不同元素)的队列
    handlerQueue = jQuery.event.handlers.call( this, event, handlers );

    // Run delegates first; they may want to stop propagation beneath us
    i = 0;
    // 遍历事件处理包{elem, handlerObjs}(取出来则对应一个包了),且事件不需要阻止冒泡
    while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
        // 定义当前Target为事件处理对象对应的元素
        event.currentTarget = matched.elem;

        j = 0;
        // 如果事件处理对象{handleObjs}存在(一个元素可能有很多handleObjs),且事件不需要立刻阻止冒泡
        while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
            // 触发的事件必须满足其一:
            // 1) 没有命名空间
            // 2) 有命名空间,且被绑定的事件是命名空间的一个子集
            if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
            
                event.handleObj = handleObj;
                event.data = handleObj.data;

                // 尝试通过特殊事件获取处理函数,否则使用handleObj中保存的handler(所以handleObj中还保存有handler)
                ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
                        .apply( matched.elem, args );

                // 如果处理函数存在
                if ( ret !== undefined ) {
                    // 如果处理函数返回值是false,则阻止冒泡,阻止默认动作
                    if ( (event.result = ret) === false ) {
                        event.preventDefault();
                        event.stopPropagation();
                    }
                }
            }
        }
    }

    // 尝试通过special.postDispatch勾住这个映射关系,未来可以优化
    if ( special.postDispatch ) {
        special.postDispatch.call( this, event );
    }

    // 返回事件函数
    return event.result;
};

这里有许多handle相关的东西,具体关系参见以下后面的函数。

jQuery.event.handlers

jQuery.event.handlers = function( event, handlers ) {
    var i, matches, sel, handleObj,
        handlerQueue = [],
        delegateCount = handlers.delegateCount,
        // 当前事件触发元素
        cur = event.target;

    // Find delegate handlers
    // Black-hole SVG <use> instance trees (#13180)
    // Avoid non-left-click bubbling in Firefox (#3861)
    // 如果有delegateCount,代表该事件是delegate类型的绑定
    // 找出所有delegate的处理函数列队
    if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
        
        // 遍历元素及元素父级节点
        for ( ; cur != this; cur = cur.parentNode || this ) {

            // 防止单机被禁用的元素时触发事件
            if ( cur.disabled !== true || event.type !== "click" ) {
                // 开始组装符合要求的事件处理对象
                matches = [];
                // 便利所有事件处理对象
                for ( i = 0; i < delegateCount; i++ ) {
                    handleObj = handlers[ i ];

                    // Don't conflict with Object.prototype properties (#13203)
                    // 选择器,用于过滤
                    sel = handleObj.selector + " ";
                    
                    // 如果matches上没有绑定该选择器数量
                    if ( matches[ sel ] === undefined ) {
                        // 在matches上绑定该选择器数量
                        matches[ sel ] = handleObj.needsContext ?
                            // 得出选择器数量,并赋值
                            jQuery( sel, this ).index( cur ) >= 0 :
                            jQuery.find( sel, this, null, [ cur ] ).length;
                    }
                    // 再次确定是否绑定选择器数量
                    if ( matches[ sel ] ) {
                        // 是则将事件处理对象推入
                        matches.push( handleObj );
                    }
                }
                // 如果得到的matches里有事件处理对象
                if ( matches.length ) {
                    // 组装成事件处理包(暂时这么叫吧),推入事件处理包队列
                    handlerQueue.push({ elem: cur, handlers: matches });
                }
            }
        }
    }

    // 如果还有事件剩余,则将剩余的装包,推入列队
    if ( delegateCount < handlers.length ) {
        handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
    }

    return handlerQueue;
}

从这里我们可以看出delegate绑定的事件和普通绑定的事件是如何分开的。

对应一个元素,一个event.type的事件处理对象队列在缓存里只有一个。

区分delegate绑定和普通绑定的方法是:delegate绑定从队列头部推入,而普通绑定从尾部推入,通过记录delegateCount来划分,delegate绑定和普通绑定。

special.setup

// 支持: Firefox 10+
// 创建冒泡的focus和blur事件,即focusin和focusout
if ( !jQuery.support.focusinBubbles ) {
    jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {

        // 通过这个参数来记录某人focusin/focusout
        var attaches = 0,
            // 
            handler = function( event ) {
                jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
            };

        // 对需要修复的特殊事件添加方法
        jQuery.event.special[ fix ] = {
            setup: function() {
                if ( attaches++ === 0 ) {
                    document.addEventListener( orig, handler, true );
                }
            },
            teardown: function() {
                if ( --attaches === 0 ) {
                    document.removeEventListener( orig, handler, true );
                }
            }
        };
    });
}

第二个分支special.setup方法主要是来在Firefox中模拟focusin和focusout事件的,因为各大主流浏览器只有他不支持这两个事件。

由于这两个方法支持事件冒泡,所以可以用来进行事件代理。

jQuery.event.simulate

然后再利用jQuery.event.simulate来模拟事件触发。

jQuery.event.simulate = function( type, elem, event, bubble ) {
    // 重写事件
    var e = jQuery.extend(
        new jQuery.Event(),
        event,
        { type: type,
            isSimulated: true,
            originalEvent: {}
        }
    );
    // 如果要冒泡
    if ( bubble ) {
        // 利用jQuery.event.trigger模拟触发事件
        jQuery.event.trigger( e, null, elem );
    } else {
        // 否则利用jQuery.event.dispatch来执行处理
        jQuery.event.dispatch.call( elem, e );
    }
    // 如果需要阻止默认操作,则阻止
    if ( e.isDefaultPrevented() ) {
        event.preventDefault();
    }
}

jQuery.fn.one

jQuery.fn.one = function( types, selector, data, fn ) {
    return this.on( types, selector, data, fn, 1 );
};

运行一次就直接调用this.on,然后在最后参数设置成只用1次就行了。

jQuery.fn.bind

jQuery.fn.bind = function( types, data, fn ) {
    return this.on( types, null, data, fn );
};

这个也是通过this.on扩展的。

jQuery.fn.live

jQuery.fn.live = function( types, data, fn ) {
    jQuery( this.context ).on( types, this.selector, data, fn );
    return this;
};

jQuery.fn.delegate

jQuery.fn.delegate = function( selector, types, data, fn ) {
    return this.on( types, selector, data, fn );
};
可见jQuery.fn.on是事件添加的核心方法,几乎所有事件添加方法都是由这一方法扩展出来的。

 

jQuery.fn.off

jQuery.fn.off = function( types, selector, fn ) {
    var handleObj, type;
    // 如果types是对象,其实现在应该说是type,并且拥有preventDefalut和handleObj
    if ( types && types.preventDefault && types.handleObj ) {
        // 通过types获取handleObj
        handleObj = types.handleObj;
        // 转成字符串来取消事件
        jQuery( types.delegateTarget ).off(
            handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
            handleObj.selector,
            handleObj.handler
        );
        return this;
    }
    // 如果types还是对象,那么认为其是是一个map,key对应事件名,value对应处理函数
    if ( typeof types === "object" ) {
        // ( types-object [, selector] )
        // 遍历所有type
        for ( type in types ) {
            this.off( type, selector, types[ type ] );
        }
        return this;
    }
    
    // 如果selector为false,或者selector是个函数
    if ( selector === false || typeof selector === "function" ) {
        // ( types [, fn] )
        // 等同于传进来types和fn
        fn = selector;
        selector = undefined;
    }
    if ( fn === false ) {
        // 如果fn是false,则定义为一个return false的函数
        fn = returnFalse;
    }
    // 遍历所有元素
    return this.each(function() {
        // 使用jQuery.event.remove删除所有事件处理
        jQuery.event.remove( this, types, fn, selector );
    });
};
 
这个方法逻辑还是比较清晰的,尝试处理各种传参方式以后,最终都是利用jQuery.event.remove来删除事件处理函数的。

jQuery.event.remove

jQuery.event.remove = function( elem, types, handler, selector, mappedTypes ) {

    var j, origCount, tmp,
        events, t, handleObj,
        special, handlers, type, namespaces, origType,
        // 通过内部缓存获取对象相关数据
        elemData = jQuery.hasData( elem ) && jQuery._data( elem );

    // 如果没有相关缓存数据,或者缓存中没有相关处理列表,则这个对象没事件可删除
    if ( !elemData || !(events = elemData.events) ) {
        // 退出
        return;
    }

    // types可能是通过空格分隔的多个type,转成数组
    types = ( types || "" ).match( core_rnotwhite ) || [""];
    t = types.length;
    // 遍历所有type
    while ( t-- ) {
        // 分解type和namespace
        tmp = rtypenamespace.exec( types[t] ) || [];
        // 得到type
        type = origType = tmp[1];
        // 得到namespace
        namespaces = ( tmp[2] || "" ).split( "." ).sort();

        // 如果type是undefined,即原来的type是.xxx.xxx之类的命名空间
        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 ];

            // 参数mappedTypes存在或当前事件和handleObj中的当前事件相同
            if ( ( mappedTypes || origType === handleObj.origType ) &&
                // 并且参数handler不存在,或handler的ID与handleObj的ID相同
                ( !handler || handler.guid === handleObj.guid ) &&
                // 并且没有命名空间,或者是handleObj的命名空间子集
                ( !tmp || tmp.test( handleObj.namespace ) ) &&
                // 并且没有selector,或者selector与handleObj的selector相同,
                // 或者selector为"**"(表示任意)并且handleObj的selector存在
                ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
                // 全部满足则删除掉当前事件对象
                handlers.splice( j, 1 );

                // 如果handleObj有selector
                if ( handleObj.selector ) {
                    handlers.delegateCount--;
                }
                // 如果特殊事件remove存在,则调用special.remove
                // 应该和special.add对应,目前应当没什么用
                if ( special.remove ) {
                    special.remove.call( elem, handleObj );
                }
            }
        }

        // 如果缓存中本来存在事件处理对象,且当前没有事件处理对象
        // 证明全部在上面循环中删除掉了,就清除掉
        // 避免潜在的特殊事件处理程序无限递归
        if ( origCount && !handlers.length ) {
            // 则尝试用special.teardown删除事件对handle的绑定
            if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
                // 不成功则使用removeEventListener删除绑定
                // 这里虽然还是这么写但实际上就是removeEventListener了
                jQuery.removeEvent( elem, type, elemData.handle );
            }

            // 删除缓存中对应事件处理函数列表
            delete events[ type ];
        }
    }

    // 如果缓存events已经空了,该对象没有任何事件绑定了
    if ( jQuery.isEmptyObject( events ) ) {
        // 在缓存中删除handle
        delete elemData.handle;

        // 清除掉events
        jQuery._removeData( elem, "events" );
    }
};
  • 实际上,主要是删除时要判断事件、处理函数、命名空间等是否匹配,匹配才能删除。
  • 还有就是,如果该事件的处理函数列队空了就需要对该事件解绑定。
  • 如果改时间的事件列表都空了,那么就将主处理器,事件列表都删掉。

然后剩下的解绑定函数都是由jQuery.fn.off扩展来的。

jQuery.fn.unbind

jQuery.fn.unbind: function( types, fn ) {
    return this.off( types, null, fn );
};

jQuery.fn.undelegate

jQuery.fn.undelegate = function( selector, types, fn ) {
    // ( namespace ) or ( selector, types [, fn] )
    return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
};

jQuery.fn.trigger

jQuery.fn.trigger = function( type, data ) {
    return this.each(function() {
        jQuery.event.trigger( type, data, this );
    });
};

jQuery.fn.trigger方法直接调用jQuery.event.trigger来模拟发消息。

下面的jQuery.fn.triggerHandler也是通过jQuery.event.trigger来模拟发消息。

jQuery.fn.triggerHandler = function( type, data ) {
    var elem = this[0];
    if ( elem ) {
        return jQuery.event.trigger( type, data, elem, true );
    }
};

jQuery.event.trigger

jQuery.event.trigger = function( event, data, elem, onlyHandlers ) {

    var i, cur, tmp, bubbleType, ontype, handle, special,
        // 需要触发事件的所有元素队列
        eventPath = [ elem || document ],
        // 指定事件类型
        type = event.type || event,
        // 事件是否有命名空间,有则分割成数组
        namespaces = event.namespace ? event.namespace.split(".") : [];

    cur = tmp = elem = elem || document;

    // 对于text和comment节点不进行事件处理
    if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
        return;
    }

    // 仅对focus/blur事件变种成focusin/out进行处理
    // 如果浏览器原生支持focusin/out,则确保当前不触发他们
    if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
        return;
    }

    // 如果type有命名空间
    if ( type.indexOf(".") >= 0 ) {
        // 重新组装事件
        namespaces = type.split(".");
        type = namespaces.shift();
        namespaces.sort();
    }
    // 看看是否需要改成ontype形式
    ontype = type.indexOf(":") < 0 && "on" + type;

    // 看看这个是不是由jQuery.Event生成的实例,否则用jQuery.Event改造
    event = event[ jQuery.expando ] ?
        event :
        new jQuery.Event( type, typeof event === "object" && event );

    // 对event预处理
    event.isTrigger = true;
    event.namespace = namespaces.join(".");
    event.namespace_re = event.namespace ?
        new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
        null;

    // 清除数据,以重新使用
    event.result = undefined;
    // 如果事件没有触发元素,则用elem代替
    if ( !event.target ) {
        event.target = elem;
    }

    // 如果data为空,则传入处理函数的是event,否则由data和event组成
    data = data == null ?
        [ event ] :
        jQuery.makeArray( data, [ event ] );

    // 尝试通过特殊事件进行处理,必要时候退出函数
    special = jQuery.event.special[ type ] || {};
    if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
        return;
    }

    // 如果需要冒泡,特殊事件不需要阻止冒泡,且elem不是window对象
    if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {

        // 冒泡时是否需要转成别的事件(用于事件模拟)
        bubbleType = special.delegateType || type;
        // 如果不是变形来的foucusin/out事件
        if ( !rfocusMorph.test( bubbleType + type ) ) {
            // 则定义当前元素师父节点
            cur = cur.parentNode;
        }
        // 遍历自身及所有父节点
        for ( ; cur; cur = cur.parentNode ) {
            // 推入需要触发事件的所有元素队列
            eventPath.push( cur );
            // 存一下循环中最后一个cur
            tmp = cur;
        }

        // 如果循环中最后一个cur是document,那么事件是需要最后触发到window对象上的
        // 将window对象推入元素队列
        if ( tmp === (elem.ownerDocument || document) ) {
            eventPath.push( tmp.defaultView || tmp.parentWindow || window );
        }
    }

    // 触发所有该事件对应元素的事件处理器
    i = 0;
    // 遍历所有元素,并确保事件不需要阻止冒泡
    while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {

        // 先确定事件绑定类型是delegateType还是bindType
        event.type = i > 1 ?
            bubbleType :
            special.bindType || type;

        // 确保缓存中该元素对应事件中包含事件处理器,
        // 则取出主处理器(jQuery handle)来控制所有分事件处理器
        handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
        // 如果主处理器(jQuery handle)存在
        if ( handle ) {
            // 触发处理器
            handle.apply( cur, data );
        }

        // 取出原生事件处理器elem.ontype
        // 比如click事件就是elem.onclick
        handle = ontype && cur[ ontype ];
        // 如果原生事件处理器存在,看看需不需要阻止事件在浏览器上的默认动作
        if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
            event.preventDefault();
        }
    }
    // 保存事件类型,因为这时候事件可能变了
    event.type = type;

    // 如果不需要阻止默认动作,立即执行
    if ( !onlyHandlers && !event.isDefaultPrevented() ) {

        // 尝试通过特殊事件触发默认动作
        if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
            !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {

            // 调用一个原生的DOM方法具有相同名称的名称作为事件的目标。
            // 例如对于事件click,elem.click()是触发该事件
            // 并确保不对window对象阻止默认事件
            if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {

                // 防止我们触发FOO()来触发其默认动作时,onFOO事件又触发了
                tmp = elem[ ontype ];

                // 清除掉该事件监听
                if ( tmp ) {
                    elem[ ontype ] = null;
                }

                // 当我们已经将事件向上起泡时,防止相同事件再次触发
                jQuery.event.triggered = type;
                // 触发事件
                elem[ type ]();
                // 完成清除标记
                jQuery.event.triggered = undefined;

                // 事件触发完了,可以把监听重新绑定回去
                if ( tmp ) {
                    elem[ ontype ] = tmp;
                }
            }
        }
    }

    return event.result;
};
 

 

二:jQuery attributes事件

jQuery attributes提供了文档节点的属性操作方法。

为了更好的理解,我们先解决jQuery core中的access参数设置函数。

jQuery.access

jQuery.access是一个专用于参数设置以及读取的方法。

jQuery链式操作中我们对BigInteger的值获取是通过方法val来获取的,很显然此时val后面不能再链式操作了。所以:

如果要读取参数值,那么这个操作就不能链式操作。

/*************************
 *    elems: 接受操作的元素组
 *    fn: 设置或读取函数
 *    key: 要设置的属性
 *    value: 要设置的值,也可以是函数
 *    chainable: 是否可以链式操作,即是读还是写操作
 *    emptyGet: 读取如果为空用什么表示
 *    raw: value能否直接传给fn,如果的确就是想将属性设置成函数value,那么要把这个值设成true
 */
jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
    var i = 0,
        length = elems.length,
        //key是否为空
        bulk = key == null;

    // 设置许多属性
    if ( jQuery.type( key ) === "object" ) {
        chainable = true;
        for ( i in key ) {
            //迭代执行设置一个值
            jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
        }

    // 只设置一个值
    } else if ( value !== undefined ) {
        chainable = true;

        //如果value不是函数
        if ( !jQuery.isFunction( value ) ) {
            raw = true;
        }

        //如果key为空
        if ( bulk ) {
            // 如果raw为true,即value不是函数
            if ( raw ) {
                //执行fn,其this为elems,参数为value
                fn.call( elems, value );
                fn = null;

            // 如果value是函数
            } else {
                //用bulk保存fn
                bulk = fn;
                //将fn变成特定形式
                fn = function( elem, key, value ) {
                    return bulk.call( jQuery( elem ), value );
                };
            }
        }
        
        //如果fn存在
        if ( fn ) {
            //遍历所有elems
            for ( ; i < length; i++ ) {
                //value是函数则运行fn(elem, key, value.call(elem, i, fn(elem, key)))
                //value非函数则运行fn(elem, key, value)
                fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
            }
        }
    }

    //如果是可链式操作的则返回elems
    return chainable ?
        elems :

        // 否则则是读取操作
        bulk ?
            fn.call( elems ) :
            // 否则如果长度不为0,则返回fn( elems[0], key ),否则返回空
            length ? fn( elems[0], key ) : emptyGet;
}

jQuery官方文档没有该函数,应该说这是jQuery的一个内部工具。主要是为了实现jQuery中设置与读取复用性逻辑以及其内循环的,也就是为了节省写代码而已。

jQuery.fn.attr()

fn也就是jQuery实例对象的方法集。

fn中的方法attr也就是我们常用的attr方法。

如获取em标签的title属性:

var title = $("em").attr("title");

那么这个函数式怎么实现的呢?

jQuery.fn.attr = function( name, value ) {
    return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
};

当参数长度为0或只为1时,则是读取操作,不能链式操作。

而这里用的设置函数是jQuery.attr,即实际运行时会运行jQuery.attr(elem, name, value)。

那么jQuery.attr是如何实现的呢?

/***************************
 *    elem: 要操作的元素
 *    name: 元素的属性名
 *    value: 要改变的值
 */
jQuery.attr = function( elem, name, value ) {
    var ret, hooks, notxml,
        nType = elem.nodeType;

    // 不处理text,comment,attribute节点
    if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
        return;
    }

    // 如果属性不支持则使用jQuery.prop
    if ( typeof elem.getAttribute === "undefined" ) {
        return jQuery.prop( elem, name, value );
    }

    //是否不是XML
    notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

    // 如果不是XML
    if ( notxml ) {
        //那么所有属性名应当是小写
        name = name.toLowerCase();
        //如果属性定义了,那么就抓住必要的钩子,解决IE6-9的相关问题
        hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
    }

    //如果value不是没有定义,即写入操作
    if ( value !== undefined ) {
        //如果value为空,则是删除操作
        if ( value === null ) {
            jQuery.removeAttr( elem, name );
        //如果钩子有set方法,则设置了之后,返回其值
        } else if ( hooks && notxml && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
            return ret;
        //否则使用setAttribute方法
        } else {
            elem.setAttribute( name, value + "" );
            return value;
        }
    //如果是读取操作,如果钩子有get方法,则通过get得到返回值
    } else if ( hooks && notxml && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
        return ret;
    //如果没有get方法
    } else {

        // IE9+中Flash对象没有.getAttrubute方法,判断防止错误
        if ( typeof elem.getAttribute !== "undefined" ) {
            ret =  elem.getAttribute( name );
        }

        // 返回undefined或则值
        return ret == null ?
            undefined :
            ret;
    }
};

jQuery.removeAttr

removeAttr = function( elem, value ) {
    var name, propName,
        i = 0,
        //分解value成为多个属性的数组
        attrNames = value && value.match( core_rnotwhite );
    
    //如果有要删除的属性名
    if ( attrNames && elem.nodeType === 1 ) {
        //遍历所有属性名
        while ( (name = attrNames[i++]) ) {
            //看看需不需要用propFix来修改名字,不用直接用name
            propName = jQuery.propFix[ name ] || name;

            // 如果属性是布尔量先改成false
            if ( rboolean.test( name ) ) {
                elem[ propName ] = false;
            }

            //删除属性
            elem.removeAttribute( name );
        }
    }
};

jQuery.propFix干了什么呢?

实际只是修改一下属性名,比如很多人喜欢用class来表示css类名,但实际上是className。

jQuery.propFix = {
    tabindex: "tabIndex",
    readonly: "readOnly",
    "for": "htmlFor",
    "class": "className",
    maxlength: "maxLength",
    cellspacing: "cellSpacing",
    cellpadding: "cellPadding",
    rowspan: "rowSpan",
    colspan: "colSpan",
    usemap: "useMap",
    frameborder: "frameBorder",
    contenteditable: "contentEditable"
};

jQuery.fn.removeAttr

这个函数实现比较简单,只是用each方法来调用jQuery.removeAttr而已。

jQuery.fn.removeAttr = function( name ) {
    return this.each(function() {
        jQuery.removeAttr( this, name );
    });
};

jQuery.fn.prop

jQuery.fn.prop = function( name, value ) {
    return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
};

可见jQuery.fn.prop和jQuery.fn.attr差不多。

jQuery.prop

jQuery.prop = function( elem, name, value ) {
    var ret, hooks, notxml,
        nType = elem.nodeType;

    if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
        return;
    }

    notxml = nType !== 1 || !jQuery.isXMLDoc( elem );

    if ( notxml ) {
        // 修复name和钩子
        name = jQuery.propFix[ name ] || name;
        hooks = jQuery.propHooks[ name ];
    }

    if ( value !== undefined ) {
        if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
            return ret;

        } else {
            return ( elem[ name ] = value );
        }

    } else {
        if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
            return ret;

        } else {
            return elem[ name ];
        }
    }
};

由于和jQuery.attr差不多,就不备注了。

jQuery.fn.removeProp

removeProp = function( name ) {
    name = jQuery.propFix[ name ] || name;
    return this.each(function() {
        // try/catch handles cases where IE balks (such as removing a property on window)
        try {
            this[ name ] = undefined;
            delete this[ name ];
        } catch( e ) {}
    });
};
removeProp相对简单些,只是通过each将所有元素的属性设为undefined然后delete掉而已。

 Attrubute和Property

从源代码我们可以发现,jQuery.attr如果找不到相应的方法会使用jQuery.prop。

jQuery 1.6加入jQuery.prop方法后,对很多人来说可能根本没啥用,因为用jQuery.attr方法肯定是对的。

但jQuery.attr和jQuery.prop到底差别在哪里呢?

这是Attrubute和Property的差别。

jQuery.attr方法会处理Attrubute和Property,但jQuery.prop只处理Property。

虽然这两个单词都可以翻译成“属性”,但是这两个实际上是不同的。

我们用一个例子来说明这个问题:

function Demo(){
    var attrs = {};
    this.name = "Bob";
    this.setAttr = function(name, value){
        attrs[name] = value;
        return value;
    }
    this.getAttr = function(name){
        return attrs[name];
    }
}

那么对于一个实例:

var i = new Demo();
i.name    //Property
i.setAttr("name", "Tom");
i.getAttr("name")    //Attrubute

所以jQuery文档中对jQuery.prop的解释是:获取在匹配的元素集中的第一个元素的属性值。

前文对属性的设置、读取、删除方法做了分解,本文继续对jQuery attributes模块分解。

jQuery.fn.addClass

/************************************
 *    value: 字符串或者是函数,字符串可以通过空格分隔className
 */
jQuery.fn.addClass = function( value ) {
    var classes, elem, cur, clazz, j,
        i = 0,
        len = this.length,
        proceed = typeof value === "string" && value;

    //如果value是函数
    if ( jQuery.isFunction( value ) ) {
        //则对所有元素迭代运行addClass
        return this.each(function( j ) {
            jQuery( this ).addClass( value.call( this, j, this.className ) );
        });
    }

    //如果value是字符串
    if ( proceed ) {
        // 对value字符串分割成数组
        classes = ( value || "" ).match( core_rnotwhite ) || [];

        //遍历元素
        for ( ; i < len; i++ ) {
            elem = this[ i ];
            //如果节点是元素,则获取原来的className
            cur = elem.nodeType === 1 && ( elem.className ?
                ( " " + elem.className + " " ).replace( rclass, " " ) :    //替换掉换行符制表符等
                " "
            );

            //如果cur不为false,即节点是元素
            if ( cur ) {
                j = 0;
                //遍历classes组装成新的className应有的值
                while ( (clazz = classes[j++]) ) {
                    if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
                        cur += clazz + " ";
                    }
                }
                //对className赋值,去掉头尾空白
                elem.className = jQuery.trim( cur );

            }
        }
    }

    return this;
};

添加class的实现上还是比较简单的,利用elem.className来赋值。需要注意:

var rclass = /[\t\r\n]/g;

jQuery.fn.removeClass

jQuery.fn.removeClass = function( value ) {
    var classes, elem, cur, clazz, j,
        i = 0,
        len = this.length,
        //参数是否正确
        proceed = arguments.length === 0 || typeof value === "string" && value;

    //如果value是函数
    if ( jQuery.isFunction( value ) ) {
        //则对所有元素迭代运行removeClass
        return this.each(function( j ) {
            jQuery( this ).removeClass( value.call( this, j, this.className ) );
        });
    }
    
    //如果参数正确
    if ( proceed ) {
        //分隔value成为class字符串数组
        classes = ( value || "" ).match( core_rnotwhite ) || [];

        //遍历
        for ( ; i < len; i++ ) {
            elem = this[ i ];
            // 获取className并进行预处理
            cur = elem.nodeType === 1 && ( elem.className ?
                ( " " + elem.className + " " ).replace( rclass, " " ) :
                ""
            );

            //如果是元素
            if ( cur ) {
                j = 0;
                //遍历所有class字符串
                while ( (clazz = classes[j++]) ) {
                    // 寻找是否有对应的字符串
                    while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
                        //有则去掉
                        cur = cur.replace( " " + clazz + " ", " " );
                    }
                }
                //给className赋值,并去掉头尾空格
                elem.className = value ? jQuery.trim( cur ) : "";
            }
        }
    }

    return this;
};

删除class的实现和addClass非常像,只是通过indexOf和replace来替换掉需要删除的class。

jQuery.fn.toggleClass

jQuery.fn.toggleClass = function( value, stateVal ) {
    var type = typeof value,
        isBool = typeof stateVal === "boolean";

    //(⊙o⊙)…不说了,大家懂得
    if ( jQuery.isFunction( value ) ) {
        return this.each(function( i ) {
            jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
        });
    }

    //遍历所有元素
    return this.each(function() {
        //如果value是字符串
        if ( type === "string" ) {
            var className,
                i = 0,
                self = jQuery( this ),
                state = stateVal,
                //将value转成classNames字符串数组
                classNames = value.match( core_rnotwhite ) || [];

            //遍历
            while ( (className = classNames[ i++ ]) ) {
                //stateVal是布尔量,则直接设置为stateVal,否则判断元素是否不存在该className
                state = isBool ? state : !self.hasClass( className );
                //如果该className不存在则添加,否则删除
                self[ state ? "addClass" : "removeClass" ]( className );
            }

        // 如果value的类型是undefined或者boolean
        } else if ( type === "undefined" || type === "boolean" ) {
            //如果元素的className存在
            if ( this.className ) {
                // 将其存入缓存
                jQuery._data( this, "__className__", this.className );
            }

            //对className赋值,为空或者缓存中的值
            this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
        }
    });
};

为了实现jQuery.fn.toggleClass还是花了很大功夫的。

缓存的利用使得toggleClass操作更加方便,而不需要记录以前是那些class。

jQuery.fn.hasClass

jQuery.fn.hasClass = function( selector ) {
    var className = " " + selector + " ",
        i = 0,
        l = this.length;
    for ( ; i < l; i++ ) {
        if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
            return true;
        }
    }

    return false;
};

这个函数通过indexOf来寻找className是否存在。

jQuery.fn.val

jQuery.fn.val = function( value ) {
    var hooks, ret, isFunction,
        elem = this[0];

    //如果没有参数
    if ( !arguments.length ) {
        //如果元素存在
        if ( elem ) {
            //得到相应的钩子
            hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];

            //通过钩子来得到值
            if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
                return ret;
            }

            //如果没得到钩子,则通过elem.value来返回值
            ret = elem.value;

            //如果ret是字符串
            return typeof ret === "string" ?
                // 将回车符替换
                ret.replace(rreturn, "") :
                // 如果ret是空的,则返回"",否则返回ret
                ret == null ? "" : ret;
        }

        return;
    }

    //value是否是函数
    isFunction = jQuery.isFunction( value );

    //遍历所有元素
    return this.each(function( i ) {
        var val,
            self = jQuery(this);

        if ( this.nodeType !== 1 ) {
            return;
        }

        //如果value是函数,则转成参数
        if ( isFunction ) {
            val = value.call( this, i, self.val() );
        } else {
            val = value;
        }

        // 将null/undefined当成""
        if ( val == null ) {
            val = "";
        // 将数字转成字符串
        } else if ( typeof val === "number" ) {
            val += "";
        //如果是数组,则遍历数组
        } else if ( jQuery.isArray( val ) ) {
            val = jQuery.map(val, function ( value ) {
                return value == null ? "" : value + "";
            });
        }

        //获取相应钩子
        hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

        // 如果钩子无法设置,则使用通常的设置方法
        if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
            this.value = val;
        }
    });
};
 
 
三:jQuery Deferred

Deferred是jQuery中对CommonJS的异步模型实现,旨在提供通用的接口,简化异步编程难度。

其是一个可链式操作的对象,提供多个回调函数的注册,以及回调列队的回调,并转达任何异步操作成功或失败的消息。

由于其对jQuery Callbacks的依赖性,如果没有概念的朋友可以查看jQuery Callbacks

jQuery.Deferred( [beforeStart ] )

创建一个Deferred对象。

beforeStart:

类型: Function( Deferred deferred )
一个在构造函数返回前运行的处理函数。

resolve、reject、notify

Defferred中定义了三种动作,resolve(解决)、reject(拒绝)、notify(通知),对应Callbacks对象的fire动作。

进而又提供了可以定义运行时的this对象的fire,即fireWith,所以又有扩展了三个对应的操作resolveWith、rejectWith、notifyWith。

内部对应的事件分别是:done(操作完成)、fail(操作失败)、progress(操作进行中),也就是Callbacks对象的add方法添加监听。

举个简单的例子,我们可以通过deferred.done注册上一个动作完成后的,那么当有地方触发了deferred.resolve或者deferred.resolveWith(这两个方法的差别在于能不能定义回调函数的this对象)时,则回调注册的函数。

其他对应的也是一样的。

代码上大概是这样的:

复制代码
var dtd = $.Deferred(); // 新建一个deferred对象
var wait = function(dtd){
    var tasks = function(){
        alert("执行完毕!");
        dtd.resolve(); // 改变deferred对象的执行状态
        };
    setTimeout(tasks,5000);
    return dtd;
};
复制代码

这样我们就有了一个5000ms延迟的wait函数。于是我们就可以这么调用:

wait(dtd).done(function(){ alert("成功了!"); })
               .fail(function(){ alert("出错啦!"); });

then

then方法提供了三种事件的注册,只要按顺序作为参数传进去就可以了。

复制代码
then: function( /* fnDone, fnFail, fnProgress */ ) {
    //分别对应完成后运行的函数,失败后运行的函数,正在运行过程中运行的函数
    var fns = arguments;
    //返回一个新的Deferred的promise,then是上一个Deferred运行后才运行的
    return jQuery.Deferred(function( newDefer ) {
        //分别对不同状态注册函数
        jQuery.each( tuples, function( i, tuple ) {
            var action = tuple[ 0 ],    //取出动作名
            fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];    //取出对应回调函数
            // 分别对当前的Deferred对象注册回调函数,也就是注册deferred[ done | fail | progress ]
            deferred[ tuple[1] ](function() {
                var returned = fn && fn.apply( this, arguments );
                //如果传进来的回调函数会返回Deferred对象则在该对象上注册事件
                if ( returned && jQuery.isFunction( returned.promise ) ) {
                    returned.promise()
                        .done( newDefer.resolve )
                        .fail( newDefer.reject )
                        .progress( newDefer.notify );
                //否则对创建出来的newDefer执行对应事件
                } else {
                    //如果上一个函数有返回值则接受传返回值,否则传上一个Deferred传来的参数
                    newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
                }
            });
        });
        fns = null;
    }).promise();
},
复制代码

Promise

Promise只提供Deferred对象中的then, done, fail, always, pipe. isResolved, 和isRejected,防止用户自行改变Deferred的状态。

完整的Deferred

复制代码
jQuery.Deferred = function( func ) {
    var tuples = [
            // 动作, 监听事件, 回调函数列队, 最终状态
            [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
            [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
            [ "notify", "progress", jQuery.Callbacks("memory") ]
        ],
        state = "pending",
        //定义promise对象
        promise = {
            //返回当前状态
            state: function() {
                return state;
            },
            //无论成功还是失败都运行回调函数
            always: function() {
                deferred.done( arguments ).fail( arguments );
                return this;
            },
            then: function( /* fnDone, fnFail, fnProgress */ ) {
            //分别对应完成后运行的函数,失败后运行的函数,正在运行过程中运行的函数
                var fns = arguments;
                //返回一个新的Deferred的promise,then是上一个Deferred运行后才运行的
                return jQuery.Deferred(function( newDefer ) {
                    //分别对不同状态注册函数
                    jQuery.each( tuples, function( i, tuple ) {
                        var action = tuple[ 0 ],    //取出动作名
                            fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];    //取出对应回调函数
                        // 分别对当前的Deferred对象注册回调函数,也就是注册deferred[ done | fail | progress ]
                        deferred[ tuple[1] ](function() {
                            var returned = fn && fn.apply( this, arguments );
                            //如果传进来的回调函数会返回Deferred对象则在该对象上注册事件
                            if ( returned && jQuery.isFunction( returned.promise ) ) {
                                returned.promise()
                                    .done( newDefer.resolve )
                                    .fail( newDefer.reject )
                                    .progress( newDefer.notify );
                            //否则对创建出来的newDefer执行对应事件
                            } else {
                                //如果上一个函数有返回值则接受传返回值,否则传上一个Deferred传来的参数
                                newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
                            }
                        });
                    });
                    fns = null;
                }).promise();
            },
            // 如果deferred存在,将promise合并到deferred里,否则返回prmoise
            promise: function( obj ) {
                return obj != null ? jQuery.extend( obj, promise ) : promise;
            }
        },
        deferred = {};
    // 向后兼容
    promise.pipe = promise.then;

    // 对deferred添加剩余的方法
    jQuery.each( tuples, function( i, tuple ) {
        //取出对应列队
        var list = tuple[ 2 ],
            //取出对应状态
            stateString = tuple[ 3 ];
        // 赋予promise[ done | fail | progress ] = list.add
        promise[ tuple[1] ] = list.add;
        // 对状态添加事件处理
        if ( stateString ) {
            list.add(function() {
                // 状态state = [ resolved | rejected ]
                state = stateString;
            // 禁用对各列队[ reject_list | resolve_list ].disable; progress_list.lock
            }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
        }

        // 分别注册方法deferred[ resolve | reject | notify ]
        deferred[ tuple[0] ] = function() {
            deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
            return this;
        };
        // 注册有with的方法
        deferred[ tuple[0] + "With" ] = list.fireWith;
    });

    // 将promise中的方法合并到deferred里
    promise.promise( deferred );

    // 如果jQuery.Deferred中的参数存在,则先用这个参数对deferred改造
    if ( func ) {
        func.call( deferred, deferred );
    }

    // 完成
    return deferred;
};
复制代码

jQuery.when

jQuery.when是一个帮助Deferred队列处理的工具,如果传单一Deferred进去,则会返回其promise,如果传多个Deferred进去,则会新建一个Deferred用以管理该Deferred队列。

  • 如果队列中有一个Deferred失败,则整个队列失败。
  • 如果队列中所有Deferred成功,则整个队列成功。
  • 如果队列中所有Deferred开始运行,则整个队列正在运行。
复制代码
jQuery.when = function( subordinate /* , ..., subordinateN */ ) {
    var i = 0,
        //将arguments转成数组
        resolveValues = core_slice.call( arguments ),
        //传入Deferred对象总数
        length = resolveValues.length,

        // 未完成的Deferred总数
        remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

        // Deferred队列管理器,如果参数只有一个Deferred则返回该Deferred
        deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

        // 更新resolve和progress的Deferred数量,全部处在这两个状态则通知管理器
        updateFunc = function( i, contexts, values ) {
            return function( value ) {
                contexts[ i ] = this;
                values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
                if( values === progressValues ) {
                    deferred.notifyWith( contexts, values );
                } else if ( !( --remaining ) ) {
                    deferred.resolveWith( contexts, values );
                }
            };
        },

        progressValues, progressContexts, resolveContexts;

    // 如果传入Deferred总量大于1,则添加事件处理
    if ( length > 1 ) {
        progressValues = new Array( length );
        progressContexts = new Array( length );
        resolveContexts = new Array( length );
        for ( ; i < length; i++ ) {
            //判断参数是不是可用的Deferred
            if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
                resolveValues[ i ].promise()
                    //单个成功则更新成功数
                    .done( updateFunc( i, resolveContexts, resolveValues ) )
                    //单个失败则整个列队失败
                    .fail( deferred.reject )
                    //单个开始运行则更新运行中的个数
                    .progress( updateFunc( i, progressContexts, progressValues ) );
            //不可用则未完成数减1
            } else {
                --remaining;
            }
        }
    }

    // 如果没有任何可用Deferred则直接通知管理器,列队完成
    if ( !remaining ) {
        deferred.resolveWith( resolveContexts, resolveValues );
    }

    //返回Promise
    return deferred.promise();
};

其主要通过内置一个Deferred来管理队列的运行状态,不过其只将Promise暴露在外,而用闭包将所有Deferred保护起来。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
jQuery中,绑定click事件并传递参数可以通过以下几种方式实现。首先,如果是通过on方法绑定事件,可以传入四个参数:jQuery.on(events,[selector],[data],fn)。其中,data参数就是用来传递参数的。另外,如果是直接通过click方法绑定事件,可以传入一个参数,也就是一个包含了参数的json对象:jQuery.click([[data],fn])。其中data参数也是用来传递参数的。在绑定函数时,需要声明参数带有event,并将参数以json对象(键值对)的格式传递。在函数内部,可以使用event.data.xxx来引用传递的参数。例如,$("选择器").click(以json对象形式存在的参数, 待绑定函数名称)。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [jquery的click()事件如何传参](https://blog.csdn.net/connie_0217/article/details/78743445)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] - *2* *3* [【JQueryJQuery绑定事件并传参](https://blog.csdn.net/weixin_45500506/article/details/119547596)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值