程式碼如下:
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的源碼,比較複雜,放棄了)
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 改變的只是鍋子裡的函數,碗裡的不受影響。
ringa_lee2017-05-18 10:53:00
可以把on可以看成把處理方法加到組件的一個事件處理數組裡,off就是從數組裡去掉這個方法。當事件觸發時,jq就會一次執行事件處理數組裡的方法,那麼如果事件處理方法裡呼叫了off會怎麼樣?文件裡已經寫的很清楚了,在當前事件觸發循環裡並不會立刻從數組裡去掉該處理方法,只有當綁定的事件再觸發時才會體現。
天蓬老师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++) {
...
}
}