前端面試題——事件代理 delegate 的實(shí)現(xiàn)(一)

當(dāng)我們需要監(jiān)聽某個(gè)元素被點(diǎn)擊的時(shí)候,我們會(huì)給這個(gè)元素添加事件監(jiān)聽器

事件監(jiān)聽器只會(huì)綁定到當(dāng)前 DOM 中已有的元素上卵渴,而實(shí)際需求中,往往會(huì)有臨時(shí)渲染出來(lái)的元素也需要監(jiān)聽事件?那這時(shí)候改怎么辦呢撩鹿?難道在每次頁(yè)面渲染結(jié)束后,都再綁定一次事件監(jiān)聽嗎悦屏?

什么是 事件代理

從字面上來(lái)理解节沦,“代理”即將自己要做的事交給別人來(lái)做。那么這邊的“事件代理”又是什么呢础爬?同樣的甫贯,如果原本有某個(gè)事件 A 是元素 a 的事件,但是 A 事件并不直接由 a 來(lái)完成看蚜,而是轉(zhuǎn)交給元素 b 來(lái)監(jiān)聽并完成

要想徹底理解事件代理的原理叫搁,我們需要先了解兩個(gè)概念:事件捕獲事件冒泡

當(dāng)有下面一段頁(yè)面結(jié)構(gòu)存在,并且點(diǎn)擊了a標(biāo)簽時(shí):

<html>
    <body>
        <div>
            <a></a>
        </div>
    </body>
</html>

事件捕獲

事件的觸發(fā)順序?yàn)?html => body => div => a供炎,即一個(gè)事件將會(huì)從最不精確的對(duì)象 (html) 開始觸發(fā)常熙,一直到最精確的一個(gè)對(duì)象 (a),四個(gè)字概括就是:由外而內(nèi)

事件冒泡

與事件捕獲剛好相反碱茁,觸發(fā)順序?yàn)?a => div => body => html裸卫,由最精確的對(duì)象開始觸發(fā),一直到最不精確的對(duì)象纽竣,是 由內(nèi)而外 式的

綁定事件到 dom 元素

想要綁定事件到元素上墓贿,需要先來(lái)看下事件監(jiān)聽器的 api :

el.addEventListener(event, action, useCapture)

在這個(gè) api 中,第一個(gè)參數(shù)是觸發(fā)事件蜓氨,如點(diǎn)擊事件 ‘click’聋袋;第二個(gè)參數(shù)是事件觸發(fā)后需要執(zhí)行的方法;第三個(gè)參數(shù)的含義是是否使用事件捕獲穴吹,將該值設(shè)為true表示在事件捕獲階段觸發(fā)

一個(gè)對(duì)象綁定一個(gè)事件

當(dāng)需要向上文頁(yè)面結(jié)構(gòu)中的a標(biāo)簽添加一個(gè)點(diǎn)擊事件時(shí)幽勒,可以用如下代碼:

const aTag = document.querySelector('a')
aTag.addEventListener('click', e => {
    console.log(e)
}) // 默認(rèn)冒泡事件機(jī)制

當(dāng)該標(biāo)簽被點(diǎn)擊時(shí)將會(huì)執(zhí)行我們定義好的回調(diào)函數(shù),這個(gè)回調(diào)函數(shù)有一個(gè)參數(shù) event(此處為了方便將參數(shù)名定義為了 e)港令,打印出來(lái)會(huì)發(fā)現(xiàn)這個(gè)參數(shù)包括了很多很多信息啥容,比如當(dāng)我們?cè)谧鐾蟿?dòng)效果的時(shí)候可能會(huì)用到的位置信息的參數(shù)等。但是其中的絕大多數(shù)參數(shù)在這次的事件代理中不會(huì)使用到

仔細(xì)查看 event 中的屬性顷霹,會(huì)發(fā)現(xiàn)有一個(gè)屬性名為target咪惠,我們知道不管是冒泡還是捕獲,都會(huì)有一個(gè)最精確的對(duì)象和一個(gè)最不精確 的對(duì)象淋淀,而這個(gè)target就是這個(gè)最精確的對(duì)象遥昧,也即我們實(shí)際點(diǎn)擊到的元素——a

(好奇的小伙伴也可以試著加上最后一個(gè)參數(shù)true,將事件機(jī)制改為捕獲,回調(diào)中的參數(shù) e 依然會(huì)有target炭臭,且指向最精確的元素)

那么如果我們將點(diǎn)擊事件綁定在a的父級(jí)div上再點(diǎn)擊a永脓,此時(shí)的e.target又會(huì)是什么呢?依然是a鞋仍,因?yàn)槲覀儗?shí)際點(diǎn)擊的對(duì)象并沒有改變常摧,a依然是最精確的對(duì)象。除非點(diǎn)擊在a以外的div區(qū)域凿试,才會(huì)使e.target變成div

多個(gè)對(duì)象綁定一個(gè)事件

<ul>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
    <li></li>
</ul>

但是實(shí)際情況是排宰,我們往往需要向多個(gè)相同的節(jié)點(diǎn)中添加事件似芝,而addEventListener這個(gè) api 只能向一個(gè)元素添加事件那婉。所以當(dāng)出現(xiàn)上面這種結(jié)構(gòu),并且需要向每一個(gè)li添加事件時(shí)党瓮,我們可能會(huì)這樣做:

// 獲取到所有的元素详炬,返回一個(gè)包含所有對(duì)應(yīng)元素的類數(shù)組
const els = document.querySelectorAll('li')
// 遍歷每個(gè)元素并未其添加點(diǎn)擊事件
Array.prototype.forEach.call(els, el => {
    el.addEventListener('click', e => {
        console.log(e)
    })
})

會(huì)發(fā)現(xiàn)上面這種方法是通過(guò)遍歷的方式一個(gè)一個(gè)地向元素添加所需要的事件,這種方式不得不說(shuō)是我們非常不愿意見到的

同時(shí)寞奸,我們只能夠向已經(jīng)存在的元素添加事件呛谜,如果通過(guò)異步請(qǐng)求數(shù)據(jù)后重新渲染了頁(yè)面,新增的節(jié)點(diǎn)該如何處理呢枪萄?難道再次執(zhí)行一遍相同的操作嗎隐岛?顯然,這很愚蠢

幾遍我們已經(jīng)確定不會(huì)有更多的新元素進(jìn)入頁(yè)面瓷翻,我們也不該使用這種方式達(dá)到我們的目的

實(shí)現(xiàn)一個(gè)事件代理

用過(guò) jQuery 的童鞋都知道聚凹,通過(guò)$(bigEl).on('click', el, () => {...})的方式添加事件綁定,并且在頁(yè)面重新渲染后也不需要再次綁定齐帚,這就是事件代理的好處:一次綁定妒牙,處處通用

那么這是如何實(shí)現(xiàn)的呢?這就要用到我們上面說(shuō)到的target屬性了对妄。原理如下:

我們先將需要執(zhí)行的事件回調(diào)綁定在一個(gè)必然存在的元素上湘今,比如body,又比如文檔節(jié)點(diǎn)document剪菱,當(dāng)我們指定的事件摩瞎,如click發(fā)生時(shí),我們就能夠獲得target屬性

由于target指向的永遠(yuǎn)是我們實(shí)際點(diǎn)擊到的元素孝常,那么我們就可以通過(guò)這個(gè)元素來(lái)判斷是不是我們所需要被點(diǎn)擊的元素愉豺,從而判斷是否執(zhí)行回調(diào)或執(zhí)行哪一個(gè)回調(diào)

這樣,即使頁(yè)面上重新增加了元素茫因,我們也不需要對(duì)這些元素進(jìn)行再次綁定

以上面的多li結(jié)構(gòu)為例蚪拦,代碼如下:

document.addEventListener('click', e => {
    if (e.target && e.target.nodeName.toUpperCase == 'LI') {
        // do something u want
        alert('u clicked li element')
    }
})

當(dāng)然,target不僅能獲取到標(biāo)簽名,也能夠獲取到 class驰贷、id 和眾多屬性盛嘿,方便我們進(jìn)行更加精確的判斷

通過(guò)這種方式,我們還能實(shí)現(xiàn):一個(gè)元素綁定多個(gè)事件括袒,多個(gè)元素綁定一個(gè)事件次兆,多個(gè)事件綁定多個(gè)元素

而所有的事件事實(shí)上并不是直接與其對(duì)應(yīng)的元素關(guān)聯(lián)的,而是統(tǒng)一掛載在一個(gè)元素上锹锰,如 document / body芥炭,通過(guò)它們間接的觸發(fā)了事件,實(shí)現(xiàn)了事件代理

事件的卸載

jQuery 中提供了一個(gè)off方法將已經(jīng)綁定的事件卸載恃慧,通過(guò)上面的學(xué)習(xí)园蝠,我們也可以實(shí)現(xiàn)這樣的事件卸載功能,只是需要繞點(diǎn)彎痢士,至于如何實(shí)現(xiàn)彪薛,我們下周再說(shuō)~~~

【結(jié)束語(yǔ):這是最近幾周以來(lái)最長(zhǎng)的一篇,希望你們喜歡怠蹂!動(dòng)動(dòng)你們的食指善延,不要吝嗇你們的喜歡哦!】

掃碼關(guān)注前端周記公眾號(hào)城侧,感謝您的支持易遣!
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市嫌佑,隨后出現(xiàn)的幾起案子豆茫,更是在濱河造成了極大的恐慌,老刑警劉巖歧强,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件澜薄,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡摊册,警方通過(guò)查閱死者的電腦和手機(jī)肤京,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)茅特,“玉大人忘分,你說(shuō)我怎么就攤上這事“仔蓿” “怎么了妒峦?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)兵睛。 經(jīng)常有香客問(wèn)我肯骇,道長(zhǎng)窥浪,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任笛丙,我火速辦了婚禮漾脂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘胚鸯。我一直安慰自己骨稿,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布姜钳。 她就那樣靜靜地躺著坦冠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪哥桥。 梳的紋絲不亂的頭發(fā)上辙浑,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音泰讽,去河邊找鬼例衍。 笑死昔期,一個(gè)胖子當(dāng)著我的面吹牛已卸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播硼一,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼累澡,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了般贼?” 一聲冷哼從身側(cè)響起愧哟,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎哼蛆,沒想到半個(gè)月后蕊梧,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡腮介,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年肥矢,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片叠洗。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡甘改,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出灭抑,到底是詐尸還是另有隱情十艾,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布腾节,位于F島的核電站忘嫉,受9級(jí)特大地震影響荤牍,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜庆冕,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一参淫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧愧杯,春花似錦涎才、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至跌前,卻和暖如春棕兼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抵乓。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工伴挚, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人灾炭。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓茎芋,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親蜈出。 傳聞我的和親對(duì)象是個(gè)殘疾皇子田弥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)铡原,斷路器偷厦,智...
    卡卡羅2017閱讀 134,702評(píng)論 18 139
  • 總結(jié): 鼠標(biāo)事件 1.click與dbclick事件$ele.click()$ele.click(handler(...
    阿r阿r閱讀 1,616評(píng)論 2 10
  • 第1章 鼠標(biāo)事件 1-1 jQuery鼠標(biāo)事件之click與dbclick事件 用交互操作中,最簡(jiǎn)單直接的操作就是...
    mo默22閱讀 1,284評(píng)論 0 6
  • 今天早上是補(bǔ)昨天的筆記燕刻,昨天太累只泼,加上晚上宿舍的燈又壞了,回到家后看了25分鐘書卵洗,想躺床上寫筆記请唱,沒寫一會(huì)兒就...
    傲嬌小貓咪閱讀 225評(píng)論 0 0
  • 秋日籍滴,一枝殘艷的月季花 /深山老林(千年桃妖) 蟬絕聲 蛙緘口 夏日已落幕 黃昏 誰(shuí)在嘆息 一枝殘艷的月季花 ...
    深山老林千年桃妖閱讀 227評(píng)論 3 2