[toc]
## 由 JS 事件引入
事件是 JS DOM 中極具活力的內容,你可以隨時監聽 DOM 的變化,并對它們及時的做出反應,如果你不是太懂 JS 中的事件,建議你先去看一些相關介紹的文章,直接看 jQuery 中的事件委托頭會頭大的。
事件的處理順序由兩個環節,一個是捕獲環節,一個是冒泡環節,借用別人的一張圖:

如果把處理也算進的話,整個事件分為三個階段,分別是捕獲階段,目標處理階段和冒泡階段。捕獲階段由外向內尋找 target,冒泡階段由內向外直到根結點。這只是一個事件,當這三個階段中又穿插著更多的事件時,還需要將事件的執行順序考慮進去。
而 jQuery 事件委托的概念:事件目標自身不處理事件,而是將其委托給父元素或祖先元素或根元素,而借助事件的冒泡性質(由內向外)來達到最終處理事件。
## jQuery 中的事件優化
首先必須要知道,綁定事件越多,瀏覽器內存占用越大,就會間接的影響性能。而且一旦出現 ajax,局部刷新導致重新綁定事件。
使用事件委托可以解決以上帶來的問題,借助事件的冒泡,尤其當一個父元素的子元素過多,而且子元素綁定的事件非常多時,委托事件的作用就體現出來了。
我本人不善于比較 JS 中的性能問題,感興趣的可以去看看這篇文章關于事件委托性能的設計和比較。深入理解-事件委托。
在早期的 jQuery 版本,使用的是 .delegate()、.bind()、.live()等方法來實現事件監聽,當然也包括.click()方法,隨著 jQuery 的發展,像 live 方法已經明確從 jQuery 中刪除,而其余的方法,比如 bind 方法也將在 3.0 之后的版本陸續刪除,取而代之的是 .on()方法。而且剩下的其它方法都是通過 on 方法來間接實現的,如果介紹,只需要看 on 的源碼即可。
on 函數在 jQuery 中的用法也很簡單,.on( events [, selector ] [, data ], handler(eventObject) )events 表示綁定的事件,比如 "click" 或 "click mouseleave",selector 和 data 是可選的,分別表示要綁定事件的元素和要執行的數據,handler 表示事件執行函數。
off 函數的用法 .off( events [, selector ] [, handler ] ),events 代表要移除的事件,selector 表示選擇的 dom,handler 表示事件處理函數。還有更殘暴的比如 .off()不接受任何參數,表示著移除所有 on 綁定的函數。
## on off 函數源碼
雖然我分析的源碼時 jQuery 3.1.1,但這個時候 bind 和 delegate 函數并沒有從源碼中移除呢,先來看看它們怎么調用 on:
```
jQuery.fn.extend( {
bind: function( types, data, fn ) {
return this.on( types, null, data, fn );
},
unbind: function( types, fn ) {
return this.off( types, null, fn );
},
delegate: function( selector, types, data, fn ) {
return this.on( types, selector, data, fn );
},
undelegate: function( selector, types, fn ) {
// ( namespace ) or ( selector, types [, fn] )
return arguments.length === 1 ?
this.off( selector, "**" ) :
this.off( types, selector || "**", fn );
}
} );
```
可以看得出來,全都被 on 和 off 這兩個函數來處理了。
```
jQuery.fn.extend( {
on: function (types, selector, data, fn) {
// on 又依托于全局的 on 函數
return on(this, types, selector, data, fn);
}
} );
function on( elem, types, selector, data, fn, one ) {
var origFn, type;
// 支持 object 的情況
if ( typeof types === "object" ) {
// ( types-Object, selector, data )
if ( typeof selector !== "string" ) {
// ( types-Object, data )
data = data || selector;
selector = undefined;
}
// 一次執行 object 的每一個
for ( type in types ) {
on( elem, type, selector, data, types[ type ], one );
}
return elem;
}
// 參數為兩個的情況
if ( data == null && fn == null ) {
// ( types, fn )
fn = selector;
data = selector = undefined;
} else if ( fn == null ) {
if ( typeof selector === "string" ) {
// ( types, selector, fn )
fn = data;
data = undefined;
} else {
// ( types, data, fn )
fn = data;
data = selector;
selector = undefined;
}
}
if ( fn === false ) {
// returnFalse 是一個返回 false 的函數
fn = returnFalse;
} else if ( !fn ) {
return elem;
}
if ( one === 1 ) {
origFn = fn;
fn = function( event ) {
// Can use an empty set, since event contains the info
jQuery().off( event );
return origFn.apply( this, arguments );
};
// Use same guid so caller can remove using origFn
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
return elem.each( function() {
// 關鍵
jQuery.event.add( this, types, fn, data, selector );
} );
}
```
是的,你沒有看錯,這個全局的 on 函數,其實只是起到了校正參數的作用,而真正的大頭是:
```
jQuery.event = {
global = {},
add: function(){...},
remove: function(){...},
dispatch: function(){...},
handlers: function(){...},
addProp: function(){...},
fix: function(){...},
special: function(){...}
}
```
off 函數:
```
jQuery.fn.off = function (types, selector, fn) {
var handleObj, type;
if (types && types.preventDefault && types.handleObj) {
// ( event ) dispatched jQuery.Event
handleObj = types.handleObj;
jQuery(types.delegateTarget).off(
handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
handleObj.selector,
handleObj.handler
);
return this;
}
if (typeof types === "object") {
// ( types-object [, selector] )
for (type in types) {
this.off(type, selector, types[type]);
}
return this;
}
if (selector === false || typeof selector === "function") {
// ( types [, fn] )
fn = selector;
selector = undefined;
}
if (fn === false) {
fn = returnFalse;
}
return this.each(function() {
// 關鍵
jQuery.event.remove(this, types, fn, selector);
});
}
```
## 總結
可見 jQuery 對于參數的放縱導致其處理起來非常復雜,不過對于使用者來說,卻非常大便利。
委托事件也帶來了一些不足,比如一些事件無法冒泡,load、submit 等,會加大管理等復雜,不好模擬用戶觸發事件等。