教育行業(yè)A股IPO第一股(股票代碼 003032)

全國(guó)咨詢/投訴熱線:400-618-4000

什么是事件委托?JavaScript事件委托的實(shí)現(xiàn)原理

更新時(shí)間:2020年10月20日10時(shí)24分 來(lái)源:傳智播客 瀏覽次數(shù):

引言:

事件委托應(yīng)用在很多開(kāi)發(fā)場(chǎng)景之中,但是很多同學(xué)對(duì)委托的原理、特別是對(duì)JS原生實(shí)現(xiàn)委托不太了解。每每看到此情此景我總覺(jué)得“眾生皆苦”,正所謂“我不寫(xiě)文章,誰(shuí)寫(xiě)文章”的普渡心態(tài),是以提供這篇文章解救眾生之苦,阿彌陀佛!

釋義

在學(xué)事件委托時(shí),我們有必要先對(duì)事件委托做一個(gè)定義。

JS里的事件委托:就是當(dāng)事件觸發(fā)時(shí),把要做的事委托給父元素來(lái)處理。

再通俗點(diǎn):就是自己的事不想干,叫它爸爸,甚至爺爺、甚至祖先來(lái)干。

作用

在學(xué)它的用法和原理之前,我們先了解一下它的作用,有什么好處。再讓各位決定是否愿意繼續(xù)看下去呢?

作用1:節(jié)約內(nèi)存(哇塞,這個(gè)好這個(gè)棒!)

作用2:能為之后新增的DOM元素依然添加事件(路人甲:這什么鬼?我:死相,真猴急。往后面看就知道了!)

揭開(kāi)事件委托面紗

場(chǎng)景1:當(dāng)多個(gè)li標(biāo)簽需要添加點(diǎn)擊事件時(shí)

代碼如圖:

事件委托01


代碼解析:

給5個(gè)li標(biāo)簽加了點(diǎn)擊事件,當(dāng)界面上點(diǎn)擊li時(shí),會(huì)打印它們各自li標(biāo)簽顯示的內(nèi)容。

出現(xiàn)的問(wèn)題:

此時(shí)5個(gè)li,看起來(lái)每個(gè)li的點(diǎn)擊事件觸發(fā)時(shí)調(diào)用的都是同一個(gè)函數(shù),即:


function(){
    console.log(this.innerHTML);             
};

但其實(shí)并不是這樣。每個(gè)li綁定的都是一個(gè)全新的函數(shù),只不過(guò)每個(gè)函數(shù)的樣子都一毛一樣。

如何驗(yàn)證這個(gè)結(jié)論呢?很簡(jiǎn)單,判斷每個(gè)li標(biāo)簽的onclick是否相等就可以了

代碼驗(yàn)證如下:

事件委托02


得到結(jié)果:

事件委托03


至于上面說(shuō)的函數(shù)長(zhǎng)得一樣,但不是同一個(gè)數(shù)據(jù)這種情況就類似于 var obj1 = {name:"jack",age:16}和var obj2 = {name:"jack",age:16},obj1 == obj2 得到的結(jié)果也會(huì)是false(如對(duì)這一塊不理解,下次有空再寫(xiě)一篇文章解答。或者來(lái)黑馬程序員實(shí)地學(xué)習(xí),課程里有講)

至此,我們可以得到結(jié)論,如果有5個(gè)li,那么就有5個(gè)函數(shù)會(huì)被創(chuàng)建在內(nèi)存中占據(jù)空間,那如果有100個(gè)li呢?就會(huì)有100個(gè)長(zhǎng)相一毛一樣的函數(shù)在內(nèi)存中常駐,對(duì)內(nèi)存的開(kāi)銷是巨大的!

解決辦法:

利用事件冒泡的原理,把事件加在父元素(ul)身上!

代碼如下:

事件委托04


原理解析:

回顧事件冒泡

事件冒泡:即一個(gè)元素的事件觸發(fā)后,會(huì)依次一級(jí)一級(jí)往上調(diào)用父級(jí)元素的同名事件,直到window(注:IE8和之前的瀏覽器只到document)

例:div > p > span 當(dāng)div和p以及span都添加了click事件,當(dāng)點(diǎn)擊span時(shí),會(huì)依次向上觸發(fā)span、p、div的click事件。

代碼如下:

事件委托05


效果如下:

事件委托06


其中,在每個(gè)觸發(fā)的事件里,通過(guò)事件對(duì)象.target能拿到 觸發(fā)事件的源頭元素 也就是 事件源。

因此,在上述代碼中,改成

事件委托07


可發(fā)現(xiàn),觸發(fā)事件時(shí),打印出來(lái)的e.target都是span,如下:

事件委托08

注:事件對(duì)象在ie8中要通過(guò)window.event才能取到,因此e = e || window.event是做兼容處理(詳細(xì)了解請(qǐng)翻閱黑馬程序員前端課程關(guān)于事件對(duì)象的講解)

解釋委托原理

在回顧完事件冒泡后,我們顯而易見(jiàn)得到結(jié)論:給所有l(wèi)i添加點(diǎn)擊事件,只要加到它們的父元素ul身上的根本原因是利用了事件冒泡。也即:無(wú)論點(diǎn)擊哪個(gè)li,都會(huì)自動(dòng)觸發(fā)ul的點(diǎn)擊事件,然后在ul里通過(guò)e.target能獲得真正被點(diǎn)擊的那個(gè)li,繼而拿到它的innerHTML

小結(jié):如果給一堆元素加事件,并且事件觸發(fā)時(shí)執(zhí)行的代碼都差不多時(shí),就可以把事件加在父元素身上啦!這樣可以更節(jié)省內(nèi)存空間哦!O(∩_∩)O哈哈~(來(lái)自摳腳大漢的賣萌符號(hào))

看,這樣的形式是不是就相當(dāng)于把自己的事件,委托在父元素身上處理了呢?因而它才會(huì)叫事件委托!

也就是:自己的活不干了,給它爹去干!(畫(huà)外音:一看就是坑爹的貨~)

存在問(wèn)題及解決方式

思考:如果ul里還有其他子元素例如span,可我只想給li加點(diǎn)擊事件,用原來(lái)寫(xiě)的事件委托還行嗎?

答案是否定的,因?yàn)楦鶕?jù)事件冒泡原理,所有子元素點(diǎn)擊后都會(huì)觸發(fā)父元素的點(diǎn)擊,因此,如果你點(diǎn)擊了span,也會(huì)調(diào)用ul的點(diǎn)擊事件,這就相當(dāng)于給span也加了點(diǎn)擊事件。這時(shí)候該怎么解決呢?

很簡(jiǎn)單,只要判斷一下事件源是不是li就行了,如果是li才執(zhí)行代碼,否則不執(zhí)行,代碼如下:

事件委托09

場(chǎng)景2: 新增元素沒(méi)有綁定事件的問(wèn)題

界面描述:界面上有一個(gè)ul里面默認(rèn)有5個(gè)li,并且還有一個(gè)按鈕,當(dāng)點(diǎn)擊按鈕就創(chuàng)建一個(gè)新的li,需要不管是默認(rèn)有的li還是新的li都有點(diǎn)擊事件。

問(wèn)題描述:

我們先嘗試不用事件委托普通寫(xiě)法會(huì)怎么寫(xiě),代碼和界面如下:

事件委托
事件委托11


JS部分代碼如下:

此時(shí)會(huì)發(fā)現(xiàn):頁(yè)面剛開(kāi)始加載時(shí)就默認(rèn)存在的5個(gè)li是有點(diǎn)擊事件的,但是點(diǎn)擊按鈕創(chuàng)建出來(lái)的li沒(méi)有點(diǎn)擊事件。

原因剖析:

因?yàn)樯厦娴腏S代碼是在頁(yè)面剛加載時(shí)執(zhí)行的,在當(dāng)時(shí)因?yàn)椴豢赡苋c(diǎn)擊按鈕,所以能找到的li標(biāo)簽只有默認(rèn)那5個(gè),因此你打印liList,發(fā)現(xiàn)也只有5個(gè)

事件委托12


因此,你遍歷liList給每個(gè)元素加點(diǎn)擊事件時(shí),只能給這5個(gè)li加到點(diǎn)擊事件。因此,如果后面再有l(wèi)i標(biāo)簽,也不擁有點(diǎn)擊事件。

使用事件委托解決

代碼如下:

事件委托13


把事件只加在ul身上,即可解決,這樣內(nèi)存占用更低,代碼也少了很多!效果如圖:

事件委托14


我們可以看到,不管是默認(rèn)有的5個(gè)li還是后面才增加的li都有點(diǎn)擊事件了

解析:因?yàn)槭录芭輽C(jī)制的存在,不管是原本有的li還是新創(chuàng)建的li,當(dāng)事件觸發(fā)時(shí)都會(huì)一級(jí)一級(jí)往上調(diào)用父元素的同名事件。因此,只要是點(diǎn)擊的li標(biāo)簽,都會(huì)觸發(fā)ul的點(diǎn)擊事件,所以只要把事件加在ul身上就解決了不管新舊li標(biāo)簽都有點(diǎn)擊事件的問(wèn)題。

jQuery里的事件委托

眾所周知,jQuery是JS的一個(gè)偉大的第三方庫(kù)(什么?你還不知道?火星了吧!趕緊來(lái)黑馬程序員惡補(bǔ)!)。JS有的方法,jQuery里都有,并且代碼寫(xiě)起來(lái)更短。因此事件委托,其實(shí)在jQuery里也存在!

jQuery事件委托語(yǔ)法:

$('父元素').on('事件名','哪個(gè)子元素觸發(fā)',傳給回調(diào)函數(shù)的參數(shù),事件觸發(fā)時(shí)的回調(diào)函數(shù));

解釋:

參數(shù)1:事件名,代表要綁定什么事件,但是記得不用加on,也就是說(shuō)如果你想加點(diǎn)擊事件,只要寫(xiě)'click'即可,注意是字符串!所以要打單引號(hào)或者雙引號(hào)

參數(shù)2:只能由哪個(gè)子元素觸發(fā),例如我寫(xiě) "li",就代表只能由這個(gè)父元素里面的li觸發(fā)事件,其他子元素不會(huì)觸發(fā)。需要注意的是,這也是字符串,并且,參數(shù)2可以不寫(xiě),那就代表僅僅只是給父元素加一個(gè)點(diǎn)擊事件,并且所有子元素都能觸發(fā)到這個(gè)事件(因?yàn)槭录芭?

參數(shù)3:其實(shí)一般不會(huì)用,就是如果想事件觸發(fā)時(shí),自己給回調(diào)函數(shù)傳一些值就寫(xiě),這個(gè)參數(shù)也可以不寫(xiě)!

參數(shù)4:事件觸發(fā)時(shí)的回調(diào)函數(shù)

總結(jié):參數(shù)1和參數(shù)4是必須的,其他是可選的,如果你要用事件委托,請(qǐng)寫(xiě)上參數(shù)2

例:

事件委托14


說(shuō)明:

1.on這個(gè)方法的參數(shù)3可以通過(guò)回調(diào)函數(shù)里的e.data拿到(但一般不會(huì)用,大家了解一下有這么個(gè)東西即可)

2.在jQuery事件委托的回調(diào)函數(shù)里this和e.target是同一個(gè)東西,但是在JS里this和e.target不是同一個(gè)東西(有興趣可以參考以前的文章或者黑馬程序員前端課程)

一般參數(shù)3不會(huì)寫(xiě),因此代碼常見(jiàn)會(huì)寫(xiě)成這樣:

事件委托15

結(jié)語(yǔ)

其實(shí)在其他語(yǔ)言里,事件委托會(huì)比較復(fù)雜,需要?jiǎng)?chuàng)建額外對(duì)象。但是由于JS的靈活性,使得在JS里僅僅只需要把事件綁定在父元素即可實(shí)現(xiàn)。

事件委托雖然在面試題中略微少見(jiàn),但是在實(shí)際開(kāi)發(fā)中幾乎都會(huì)用到。因?yàn)橛袝r(shí)候需要給某一類元素加事件(例如給所有l(wèi)i加點(diǎn)擊事件),因?yàn)榫W(wǎng)頁(yè)內(nèi)容經(jīng)常改變,這類元素隨時(shí)會(huì)增加或者減少,為了保證所有這類元素都有事件,也為了節(jié)約內(nèi)存,所以都需要采用事件委托才可實(shí)現(xiàn)!

最后含著淚,摳著腳跟大伙say byebye,我們下期再見(jiàn)~!


猜你喜歡:

Js中深拷貝與淺拷貝的區(qū)別 

目前最好的js異步 

傳智播客web前端培訓(xùn)課程 

黑馬程序員web前端培訓(xùn)課程 

0 分享到:
和我們?cè)诰€交談!