首页  >  问答  >  正文

javascript - jQuery事件绑定on和off的问题

代码如下:

var $test = $(document);
  
function handler1() {
  console.log( "handler1" );
  $test.off( "click", handler2 );
}
  
function handler2() {
  console.log( "handler2" );
}
  
$test.on( "click", handler1 );
$test.on( "click", handler2 );

这段代码为什么第一次点击的时候会输出handler1和handler2,handler1中的off起作用了但是仍然会执行一次handler2,原因是什么?
参考jQuery文档

文档里解释的是: Adding or removing event handlers on the current element won't take effect until the next time the event is handled
也许是我英语太渣,我感觉这个地方表述的不是很准确,应该是同一事件的添加或删除在当前处理过程中无效,比如我把两个click改成如下

var $test = $(document);
  
function handler1() {
  console.log( "handler1" );
  $test.off( "mouseup", handler2 );
}
  
function handler2() {
  console.log( "handler2" );
}
  
$test.on( "mousedown", handler1 );
$test.on( "mouseup", handler2 );

那就只会输出handler1,可见在mousedown里off掉mouseup是成功的

回到最开始,为什么同一事件的添加或删除在当前处理过程中无效?(我试着找了找jQuery的源码,比较复杂,放弃了)

迷茫迷茫2684 天前498

全部回复(3)我来回复

  • ringa_lee

    ringa_lee2017-05-18 10:53:00

    还是得从源码开始讲解

    先看这里: https://github.com/jquery/jqu...

    handlerQueue = jQuery.event.handlers.call( this, event, handlers );

    此处为事件分发的核心代码开始处,可见jquery分发事件时需调用 this.event.handler 求出事件回调的列表,此处 handlerQueue 只是内部事件列表一部分的拷贝,而你用 off 改变的是内部事件列表,对拷贝出的 handlerQueue 自然没有影响。

    就像是全部的事件回调函数都存在锅里,当有事件发生时,jquery 会把一部回调拷贝,盛到碗里,然后依次执行碗里的函数。你 off 改变的仅是锅里的函数,碗里的不受影响。

    回复
    0
  • ringa_lee

    ringa_lee2017-05-18 10:53:00

    可以把on可以看成把处理方法加到组件的一个事件处理数组里,off就是从数组里去掉这个方法。当事件触发时,jq就会一次执行事件处理数组里的方法,那么如果事件处理方法里调用了off会怎么样?文档里已经写的很清楚了,在当前事件触发循环里并不会立刻从数组里去掉该处理方法,只有当绑定的事件再触发时才会体现。

    回复
    0
  • 天蓬老师

    天蓬老师2017-05-18 10:53:00

    感谢大家
    问题找到了,原因在于jq内部对于事件的封装处理时,同一类型的事件会添加到同一个数组中,而在触发事件时遍历又是这个数组的一个复制,off没有改变复制的数组,所以导致第一次click的时候输出了hanler2,而对于mousedown和mouseup,因为是不同类型的事件,是封装成两个数组的,所以在mousedown中off掉mouseup是有效的。也许表达的不是很好,贴几行主要的jq源码

    //获取触发的事件的数组,在上面的代码中触发click时这个数组中是有两个值的,而触发mousedown时是只有一个值的,这个值是内部封装的一个handleObj对象
    handlers = ((jQuery._data(this, "events") || {})[event.type] || [])
    
    ...
    
    //之后会把这个handlers数组根据是否委托来slice(也是这个地方复制了handlers,导致click中的off第一次点击的时候无效),然后添加到数组handlerQueue中
    handlerQueue.push({ elem: this, matches: handlers.slice(delegateCount) })
    
    ...
    //最后触发的时候是遍历handlerQueue中的matches来执行
     for (i = 0; i < handlerQueue.length && !event.isPropagationStopped() ; i++) {
                    matched = handlerQueue[i];
                    event.currentTarget = matched.elem;
    
                    for (j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped() ; j++) {
                        ...
                        }
    }

    回复
    0
  • 取消回复