引言
這是一個(gè)簡(jiǎn)單的需求夭问,衍生的思考哮缺。
昨天QA提了一個(gè)富文本優(yōu)化的需求,當(dāng)插入視頻和音頻時(shí)甲喝,如果用戶(hù)同時(shí)點(diǎn)擊了視頻尝苇、音頻,那么其他的視頻或者音頻應(yīng)該停止播放埠胖,只有最后一個(gè)點(diǎn)擊的才會(huì)是播放狀態(tài)糠溜。
需求很簡(jiǎn)單(天真的想法),稍微理清下思路:
- 多媒體文件插入之后直撤,獲取dom內(nèi)所有的多媒體元素
- 循環(huán)給他們添加事件非竿,在添加事件前,remove掉之前的事件
- 當(dāng)任何一個(gè)元素點(diǎn)擊之后红柱,遍歷多媒體文件列表,如何不是自己蓖乘,其他的所有的都暫停就好了
三步搞定锤悄。
上代碼
onEmbed() {
const item = document.querySelectorAll('video,audio')
item.forEach((element, kindex) => {
const clickFun = e => {
item.forEach((i, index) => {
// 內(nèi)容中包含音頻和視頻時(shí),音頻和視頻能同時(shí)播放
if (index !== kindex) {
i.pause()
}
})
}
element.removeEventListener('play', clickFun, false)
element.addEventListener('play', clickFun, false)
})
},
代碼看上去完美嘉抒,運(yùn)行下零聚,發(fā)現(xiàn)沒(méi)有問(wèn)題,當(dāng)多個(gè)視頻點(diǎn)擊時(shí)些侍,永遠(yuǎn)只有最后一個(gè)視頻點(diǎn)擊會(huì)播放隶症,其他的只會(huì)暫停,
如果事情這么簡(jiǎn)單岗宣,那就不會(huì)有這次blog記錄了蚂会,這個(gè)代碼有嚴(yán)重的性能問(wèn)題,有興趣的耗式,可以停下來(lái)思考一分鐘
console.log("思考是不可能的胁住,我只想立馬知道答案!")
好了纽什,現(xiàn)在揭露措嵌,如果在clickFun
事件中,打印下console.log(e)
芦缰,就會(huì)發(fā)現(xiàn)每多次添加一個(gè)視頻,e
就會(huì)輸出多次让蕾,類(lèi)似下圖浪规,這樣隨著添加的元素變多探孝,那么就會(huì)造成這個(gè)事件調(diào)用多次,所以 還需要進(jìn)一步改進(jìn)缸濒,同時(shí)也得查清楚removeEventListener
為啥沒(méi)有效果。
removeEventListener介紹
仔細(xì)閱讀了下代碼庇配,應(yīng)該是clickFun
這個(gè)事件绍些,每次生成,都會(huì)new一個(gè)柬批,而removeEventListener
要remove掉addEventListener
添加的事件,function
必須是一個(gè)引用對(duì)象
https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/removeEventListener
addEventListener
方法將指定的監(jiān)聽(tīng)器注冊(cè)到 EventTarget
上 , 然后removeEventListener
內(nèi)會(huì)按照嗅虏,type
、listener 參數(shù)
旋恼、useCapture
這三個(gè)參數(shù)來(lái)刪除 EventTarget
內(nèi)的Event,如果有一處不一樣冰更,就會(huì)失敗昂勒。
https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener
現(xiàn)在就是listener
因?yàn)槊看味紩?huì)觸發(fā),所以?xún)?nèi)存中一直都是new一個(gè) 戈盈,所以導(dǎo)致無(wú)法remove
填坑
方法一: 找到目標(biāo)元素,遍歷所有的event归斤,然后remove掉就好了,比如說(shuō)chrome內(nèi)的Event Listeners工具脏里,監(jiān)測(cè)元素的事件,然后remove掉就好了
可惜這個(gè)方法迫横,目前只有在chrome瀏覽器Console
內(nèi)才有效,它還沒(méi)有成為標(biāo)準(zhǔn)矾踱。
2015年W3C官方博客宣布成立WICG小組(Web Platform Incubator Community Group),號(hào)召開(kāi)發(fā)者把可行的Web平臺(tái)新特性都提交到這個(gè)小組內(nèi)呛讲,其中就有getEventListeners
,雖然已經(jīng)提了2年了贝搁,但是好像還沒(méi)有什么進(jìn)展。
另外在github上也有人向WHATWG組織(很牛的一個(gè)組織徘公,不比W3C差)提了一個(gè)issue,有更詳細(xì)的建議坦袍,也有好多人在討論,個(gè)人覺(jué)得WHATWG會(huì)更快的把它設(shè)為新特性捂齐。
方法二: 按照removeEventListener的原理缩抡,只要保證type
、listener 參數(shù)
瞻想、useCapture
這三個(gè)參數(shù)一致,肯定就可以刪除了蘑险,那么把clickFun
移出循環(huán)內(nèi)應(yīng)該就可以了。
看代碼
// clickFun放置到最外層佃迄,保證只生成一次
const clickFun = e => {
const item = document.querySelectorAll('video,audio')
item.forEach((i, index) => {
// 內(nèi)容中包含音頻和視頻時(shí),音頻和視頻能同時(shí)播放
if (index !== Number(e.target.getAttribute('kindex'))) {
i.pause()
}
})
}
onEmbed() {
const item = document.querySelectorAll('video,audio')
item.forEach((element, kindex) => {
element.setAttribute('kindex', kindex)
element.removeEventListener('play', clickFun, false)
element.addEventListener('play', clickFun, false)
})
}
代碼運(yùn)行后堆缘,完美,一切都按照最初的設(shè)想運(yùn)行下去吼肥。
事情到這里結(jié)束了嗎?并沒(méi)有潜沦,現(xiàn)在回想方法一和removeEventListener的介紹绪氛,我們前端如果需要remove掉某個(gè)dom節(jié)點(diǎn)的某個(gè)類(lèi)型所有的事件,應(yīng)該如何處理枣察?如果需要查看某個(gè)節(jié)點(diǎn)所有事件應(yīng)該如何處理?而且目前 現(xiàn)在原生add序目、remove事件真的好難用 ,有沒(méi)有更簡(jiǎn)單的寫(xiě)法猿涨。
這就是新的需求,后面一個(gè)問(wèn)題叛赚,還可以通過(guò)chrome Event Listeners工具查詢(xún),但是通過(guò)編碼操作就不行了俺附。
其實(shí)仔細(xì)想想,關(guān)于第一個(gè)問(wèn)題也很好解決 事镣,如果我們拿不到節(jié)點(diǎn)的Event,那每次添加Event前氛琢,把Event存儲(chǔ)到一個(gè)列表內(nèi),如果要remove阳似,只要遍歷這個(gè)列表就可以了,這樣所有的的Event都可以管理了障般,以后要操作也方便了。
好了挽荡,思想已經(jīng)有了,接下來(lái)就是擼代碼了定拟。
type Capture = boolean | AddEventListenerOptions;
interface EventHandler {
[handlers: string]: [[EventListenerOrEventListenerObject, Capture]] | [];
}
const isProperty = (obj, property) =>
Object.prototype.hasOwnProperty.call(obj, property);
const dom = {
eventMap: new Map<Element, EventHandler>(),
addListener(
node: Element,
event: string,
handler: EventListenerOrEventListenerObject,
capture: Capture
) {
if (!this.eventMap.has(node)) {
this.eventMap.set(node, Object.create(null));
}
// @ts-ignore
const targetHandlers: EventHandler = this.eventMap.get(node);
if (!isProperty(targetHandlers, event)) {
targetHandlers[event] = [];
}
// @ts-ignore
targetHandlers[event].push([handler, capture]);
node.addEventListener(event, handler, capture);
},
removeListeners(node, event) {
if (!this.eventMap.has(node)) {
return false;
}
// @ts-ignore
const targetHandlers: EventHandler = this.eventMap.get(node);
if (!isProperty(targetHandlers, event)) {
return false;
}
targetHandlers[event].forEach(th =>
node.removeEventListener(event, th[0], th[1])
);
}
};
export default dom;
代碼很簡(jiǎn)單,不到50行株依,但是基本上實(shí)現(xiàn)了,簡(jiǎn)單的添加恋腕、刪除、羅列已有監(jiān)聽(tīng)事件列表荠藤。
刪除事件也不用傳 handler获高,比之前方便了很多。
dom.addListener(evn, "click", clickfun, false);
dom.removeListeners(evn, "click");
通過(guò)導(dǎo)出的api可以很方便的查看念秧,某個(gè)node下有多少類(lèi)型事件
最后把代碼整理了下,做了一個(gè)demo摊趾,放在了codesandebox,所有的代碼都在里面