@拭目以待:原生js實(shí)現(xiàn)on與off 方法
使用過jQuery的同學(xué)上沐,應(yīng)該對(duì)事件綁定方法 .on() .off() 有一定的了解参咙。 在個(gè)人類庫 jTool 中實(shí)現(xiàn)了這兩個(gè)方法硫眯,這里就來細(xì)說下原生實(shí)現(xiàn)方式。
**注意: **
以下為個(gè)人類庫 jTool 中 Event 實(shí)現(xiàn)方式净宵。
代碼中使用到一個(gè)基礎(chǔ)方法對(duì)象 utilities, 該對(duì)象為 jTool 的基礎(chǔ)類紧武。 如果想了解更多脏里,可以通過點(diǎn)擊進(jìn)入查看原碼虹曙。
首先通過一個(gè)空架子來了解下實(shí)現(xiàn)邏輯番舆,核心實(shí)現(xiàn)在getEventObject() 方法內(nèi)的包裝函數(shù):
var Event = {
// 綁定
on: function(event, querySelector, callback, useCapture){
return this.addEvent(this.getEventObject(event, querySelector, callback, useCapture));
}恨狈,
// 解除綁定
off: function(event, querySelector) {
return this.removeEvent(this.getEventObject(event, querySelector));
},
// 獲取 jTool Event 對(duì)象
getEventObject: function (event, querySelector, callback, useCapture){
// 事件代理實(shí)現(xiàn)核心
// 注意: 這個(gè)方法為包裝函數(shù),此處的this為觸發(fā)事件的Element
var fn = callback;
callback = function(e){
// 驗(yàn)證子選擇器所匹配的nodeList中是否包含當(dāng)前事件源 或 事件源的父級(jí)
// 注意: 這個(gè)方法為包裝函數(shù),此處的this為觸發(fā)事件的Element
var target = e.target;
while(target !== this ){
if([].indexOf.call( this.querySelectorAll(querySelector), target) !== -1){
fn.apply(target, arguments);
break;
}
target = target.parentNode;
}
};
return {
eventName: event + querySelector,
type: event,
querySelector: querySelector,
callback: callback || utilities.noop,
useCapture: useCapture || false
};
},
// 增加事件,并將事件對(duì)象存儲(chǔ)至DOM節(jié)點(diǎn)
addEvent: function (eventList){
}禾怠,
// 刪除事件,并將事件對(duì)象移除出DOM節(jié)點(diǎn)
removeEvent: function (eventList){
}
}
參數(shù)說明
event: 事件名, 如 'click', 并支持 'click.scope1' 事件域的方法(雖然支持芽偏,但由于暫時(shí)沒有使用場景弦讽。所以僅對(duì)參數(shù)進(jìn)行了處理往产,功能上沒有實(shí)現(xiàn))
querySelector: 子選擇器
callback: 事件觸發(fā)后執(zhí)行的函數(shù)
useCapture: 指定事件是否在捕獲或冒泡階段執(zhí)行.true - 事件句柄在捕獲階段執(zhí)行 false- 默認(rèn)。事件句柄在冒泡階段執(zhí)行
eventList: 由 .getEventObject() 方法生成的 Event 對(duì)象
on的實(shí)現(xiàn)邏輯
通過調(diào)用jTool實(shí)例的on方法锐朴,傳入?yún)?shù)
調(diào)用getEventObject() 獲取事件對(duì)象
調(diào)用addEvent() 方法進(jìn)行事件綁定
off的實(shí)現(xiàn)邏輯
通過調(diào)用jTool實(shí)例的off方法蔼囊,傳入?yún)?shù)
調(diào)用getEventObject() 獲取事件對(duì)象
調(diào)用removeEvent() 方法解除事件綁定
完整Event對(duì)象
var _Event = {
on: function(event, querySelector, callback, useCapture) {
// 將事件觸發(fā)執(zhí)行的函數(shù)存儲(chǔ)于DOM上, 在清除事件時(shí)使用
},
off: function(event, querySelector) {
return this.removeEvent(this.getEventObject(event, querySelector));
},
bind: function(event, callback, useCapture) {
return this.on(event, undefined, callback, useCapture);
},
unbind: function(event) {
return this.removeEvent(this.getEventObject(event));
},
// 獲取 jTool Event 對(duì)象
getEventObject: function(event, querySelector, callback, useCapture) {
// $(dom).on(event, callback);
if (typeof querySelector === 'function') {
useCapture = callback || false;
callback = querySelector;
querySelector = undefined;
}
// event callback 為必要參數(shù)
if (!event) {
utilities.error('事件綁定失敗,原因: 參數(shù)中缺失事件類型');
return this;
}
// 子選擇器不存在 或 當(dāng)前DOM對(duì)象包含Window Document 則將子選擇器置空
if(!querySelector || utilities.type(this.DOMList[0]) !== 'element'){
querySelector = '';
}
// #Event003 存在子選擇器 -> 包裝回調(diào)函數(shù), 回調(diào)函數(shù)的參數(shù)
// 預(yù)綁定功能實(shí)現(xiàn)
if(querySelector !== ''){
var fn = callback;
callback = function(e){
// 驗(yàn)證子選擇器所匹配的nodeList中是否包含當(dāng)前事件源 或 事件源的父級(jí)
// 注意: 這個(gè)方法為包裝函數(shù),此處的this為觸發(fā)事件的Element
var target = e.target;
while(target !== this ){
if([].indexOf.call( this.querySelectorAll(querySelector), target) !== -1){
fn.apply(target, arguments);
break;
}
target = target.parentNode;
}
};
}
var eventSplit = event.split(' ');
var eventList = [],
eventScopeSplit,
eventObj;
utilities.each(eventSplit, function(i, eventName) {
if (eventName.trim() === '') {
return true;
}
eventScopeSplit = eventName.split('.');
eventObj = {
eventName: eventName + querySelector,
type: eventScopeSplit[0],
querySelector: querySelector,
callback: callback || utilities.noop,
useCapture: useCapture || false,
// TODO: nameScope暫時(shí)不用
nameScope: eventScopeSplit[1] || undefined
};
eventList.push(eventObj);
});
return eventList;
},
// 增加事件,并將事件對(duì)象存儲(chǔ)至DOM節(jié)點(diǎn)
addEvent: function(eventList) {
var _this = this;
utilities.each(eventList, function (index, eventObj) {
utilities.each(_this.DOMList, function(i, v){
v.jToolEvent = v.jToolEvent || {};
v.jToolEvent[eventObj.eventName] = v.jToolEvent[eventObj.eventName] || [];
v.jToolEvent[eventObj.eventName].push(eventObj);
v.addEventListener(eventObj.type, eventObj.callback, eventObj.useCapture);
});
});
return _this;
},
// 刪除事件,并將事件對(duì)象移除出DOM節(jié)點(diǎn)
removeEvent: function(eventList) {
var _this = this;
var eventFnList; //事件執(zhí)行函數(shù)隊(duì)列
utilities.each(eventList, function(index, eventObj) {
utilities.each(_this.DOMList, function(i, v){
if (!v.jToolEvent) {
return;
}
eventFnList = v.jToolEvent[eventObj.eventName];
if (eventFnList) {
utilities.each(eventFnList, function(i2, v2) {
v.removeEventListener(v2.type, v2.callback);
});
v.jToolEvent[eventObj.eventName] = undefined;
}
});
});
return _this;
}
};
.on() 與 .bind() 都可以進(jìn)行事件綁定滴肿, 區(qū)別在于 .on() 使用事件代理。 事件代理是一種事件預(yù)綁定機(jī)制贵少, 該機(jī)制可以在事件節(jié)點(diǎn)不存在的情況下, 將事件綁定至已存在父級(jí)節(jié)點(diǎn)之上的方式滔灶。
源碼鏈接
隨筆一行
這是前端最好的時(shí)代录平, 這也是前端最壞的時(shí)代。 眾多前端框架滿天飛动猬,隨著 jQuery 在前端行業(yè)的慢慢弱化表箭,總是會(huì)有一種斯人遠(yuǎn)去,何者慰籍的感覺彼水〖颍互勉吧,各位叛赚。
@拭目以待
個(gè)人站點(diǎn):www.lovejavascript.com
表格管理插件:gridmanager.lovejavascript.com && github地址
QQ交流群 (452781895):How To Make Love
《野生前端工程師》專輯中所有文章均為@拭目以待原創(chuàng)稽揭,轉(zhuǎn)載請(qǐng)注明出處溪掀。