首頁  >  問答  >  主體

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,可見在mousedownoff掉mouseup是成功的

回到最開始,為什麼同一事件的新增或刪除在目前處理過程中無效? (我試著找了找jQuery的源碼,比較複雜,放棄了)

迷茫迷茫2734 天前521

全部回覆(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
  • 取消回覆