使用jq框架,如何让事件在捕获阶段执行。

比如在父元素A设置一个事件,在子元素B设置另一个事件。
点击子元素的时候,我希望A的事件先触发(就是按照捕获阶段执行)
但是我看过源码,jq绑定事件的addEventlistener只能冒泡执行。
这令我不解,难道jq没有给出解决方法吗?
这种情况应该有人遇到过才对,是怎么解决的呢?

$(box).on('click',function(e){console.log(0)}); //并不是最先触发的
$(b1).on('click',function(e){console.log(1)});
$(box).on('click.blur','.b1',function(e){console.log(2)});
阅读 7.3k
4 个回答
✓ 已被采纳新手上路,请多包涵

看来是jq本身没有打算为事件控制捕获顺序了。
那么我试着假设要为jq框架的基础上添加一个这样的功能,修改原先的程序。
首先为绑定事件(event.add)增加一个可选参数capture,用于决定捕获顺序。
正常来说jq只会为一个元素绑定一个addEventListener。但为了能按照捕获顺序触发函数,接收到有capture的事件,需额外绑定一个capture为true的addEventListener。
为jq事件处理对象(handlers)加上capture属性。
然后修改event.handlers方法,按照事件传播阶段(eventPhase)来判断现在触发的是捕获还是冒泡触发。如果是捕获触发,在处理程序列表(handlerQueue)内插入capture为true的handlers。然后就能按照正常流程执行处理程序。
以上的修改能保留原先的构造上增加捕获触发的功能,也能使用jq.off(解除绑定事件)。希望以后jq能增加这个功能吧。

原来是我理解错了,多亏阿蛇提醒,查了资料,jquery自身并未提供在捕获阶段触发事件处理函数的接口。原生的addEventListener可以实现,非要和jq扯上关系,改造如下(用了阿蛇的html结构)

    <div id="xx" style="height:200px;background:green;">
        <div id="yy" style="position:relative;top:50px;height:100px;background:red;"></div>
    </div>
    <script>
        $('#yy')[0].addEventListener("click",function(){
            alert("子")
        },true);
        $('#xx')[0].addEventListener("click",function(){
            alert("父")
        },true);

    </script> 

clipboard.png
本质是这个。
既然jquery没有捕获的实现,我们自己实现一个。代码如下

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
    <!--换成你自己jquery的地址-->
    <script src="jquery-1.12.4.js"></script>
    <title>Document</title>
</head>
<body>
    <div id="xx" style="height:200px;background:green;">
        <div id="yy" style="position:relative;top:50px;height:100px;background:red;"></div>
    </div>
    <script>
        (function(debug){
            // 用WeakMap省的dom被移除了还持有引用无法gc
            const listeners = new WeakMap();
            // core
            const capture = (elem,type,callback,once=false) => {
                // 看看有没有这个dom,避免重复注册
                if(!listeners.has(elem)){
                    listeners.set(elem,new Map());
                    // https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
                    elem.addEventListener(type,fnCenter,{
                        capture:true,
                        once,
                    });
                }
                const map = listeners.get(elem);
                // 看看有没有这个type的事件被注册
                if(map.has(type)){
                    map.get(type).push(callback);
                }else{
                    map.set(type,[callback]);
                }
            };
            
            // 所有注册都用同一个函数引用,方便解除
            const fnCenter = function(event){
                // currentTarget和target的区别参见mdn
                const {currentTarget,type} = event;
                // currentTarget,type就是找到回调的两把钥匙
                if(listeners.has(currentTarget) && listeners.get(currentTarget).has(type)){
                    const callbacksArray = listeners.get(currentTarget).get(type);
                    // 循环执行回调们
                    callbacksArray.forEach(callback=>callback(event));
                }else{
                    //找不到么应该有问题,你可以选择在此抛出错误或者警告或者移除注册
                }
            }
            
            // 卸载
            const remove = (elem,type) => {
                // 看看注没注册过,没注册过就退出
                if(!listeners.has(elem)){
                    return false;
                }
                // 没写type全移除,写了type,只移除这个类型的
                if(!type){
                    listeners.delete(elem);
                }else{
                    const map = listeners.get(elem);
                    map.delete(type);
                }
                elem.removeEventListener(type,fnCenter);
            }
            
            // 注册到jQuery的原型上去
            // 监听捕获
            $.fn.capture=function(type,callback){
                // this is a jQuery Object
                if(!this.length){
                    throw new Error("没有有效的selector")
                }
                for(let i=0;i<this.length;i++){
                    const dom = this.get(i);
                    capture(dom,type,callback);
                }
            };
            // 仅监听一次捕获
            $.fn.captureOnce=function(type,callback){
                // this is a jQuery Object
                if(!this.length){
                    throw new Error("没有有效的selector")
                }
                for(let i=0;i<this.length;i++){
                    const dom = this.get(i);
                    capture(dom,type,callback,true);
                }
            };
            // 解除监听
            $.fn.removeCapture=function(type){
                // this is a jQuery Object
                if(!this.length){
                    throw new Error("没有有效的selector")
                }
                for(let i=0;i<this.length;i++){
                    const dom = this.get(i);
                    remove(dom,type);
                }
            };
            
            if(debug){
                window.listeners = listeners;
            }
            
        })(true);
        
        // 实验
        $("#xx").capture("click",function(e){
            alert("父");
        });
        $("#yy").capture("click",function(e){
            alert("子");
        });
    </script> 
</body>
</html>

效果如下

clipboard.png

推荐问题