『壹』 事件代理和事件委託
事件委託就是利用事件冒泡,只指定一個事件處理程序,就可以管理某一類型的所有事件,事件委託又叫事件代理。
舉一個通俗的例子:有三個同事預計會在周一收到快遞。為簽收快遞,有兩種辦法:一是三個人在公司門口等快遞;二是委託給前台代為簽收。現實當中,我們大都採用委託的方案
前台收到快遞後,會判斷收件人是誰,然後按照收件人的要求簽收,甚至代為付款。這種方案還有一個優勢,那就是即使公司里來了新員工,前台也會在收到寄給新員工的快遞後核實並代為簽收。
這里其實還有2層意思的:
一般來說,dom需要有事件處理程序,我們都會直接給它設事件處理程序就好了,那如果是很多的dom需要添加事件處理呢?比如我們有100個li,每個li都有相同的click點擊事件,可能我們會用for循環的方法,來遍歷所有的li,然後給它們添加事件,那這么做會存在什麼影響呢?
在javaScript中,添加到頁面上的事件處理程序數量將直接關繫到頁面的整體運行性能,因為需要不斷的與dom節點進行交互,訪問dom的次數越多,引起瀏覽器重繪與重排的次數也就越多,就會延長整個頁面的交互就緒時間,這就是為什麼性能優化的主要思想之一就是減少DOM操作的原因;如果要用事件委託,就會將所有的操作放到js程序裡面,與dom的操作就只需要交互一次,這樣就能大大的減少與dom的交互次數,提高性能;
每個函數都是一個對象,是對象就會佔用內存,對象越多,內存佔用率就越大,自然性能就越差了,比如上面的100個li,就要佔用100個內存空間,如果是1000個,10000個呢,如果用事件委託,那麼我們就可以只對它的父級(如果只有一個父級)這一個對象進行操作,這樣我們就需要一個內存空間就夠了,性能就會更好。
事件委託是利用事件的冒泡原理來實現的,何為事件冒泡呢?就是事件從最深的節點開始,然後逐步向上傳播事件,舉個例子:頁面上有這么一個節點樹,div>ul>li>a;比如給最裡面的a加一個click點擊事件,那麼這個事件就會一層一層的往外執行,執行順序a>li>ul>div,有這樣一個機制,那麼我們給最外面的div加點擊事件,那麼裡面的ul,li,a做點擊事件的時候,都會冒泡到最外層的div上,所以都會觸發,這就是事件委託,委託它們父級代為執行事件。
介紹了瀏覽器的事件冒泡機制,這里再詳細介紹一下瀏覽器處理DOM事件的過程。對於事件的捕獲和處理,不同的瀏覽器廠商有不同的處理機制,這里我們主要介紹W3C對DOM2.0定義的標准事件。DOM2.0模型將事件處理流程分為三個階段:一、事件捕獲階段,二、事件目標階段,三、事件起泡階段。如圖:
事件捕獲:當某個元素觸發某個事件(如onclick),頂層對象document就會發出一個事件流,隨著DOM樹的節點向目標元素節點流去,直到到達事件真正發生的目標元素。在這個過程中,事件相應的監聽函數是不會被觸發的。
事件目標:當到達目標元素之後,執行目標元素該事件相應的處理函數。如果沒有綁定監聽函數,那就不執行。
事件起泡:從目標元素開始,往頂層元素傳播。途中如果有節點綁定了相應的事件處理函數,這些函數都會被一次觸發。如果想阻止事件起泡,可以使用e.stopPropagation()(Firefox)或者e.cancelBubble=true(IE)來阻止事件的冒泡傳播。
1、在介紹事件委託的方法之前,我們先來看一段一般方法的例子:子節點實現相同的功能,實現功能是點擊li,彈出123:
我們看到有多少次的dom操作,首先要找到ul,然後遍歷li,然後點擊li的時候,又要找一次目標的li的位置,才能執行最後的操作,每次點擊都要找一次li;那麼我們用事件委託的方式做又會怎麼樣呢?
這里用父級ul做事件處理,當li被點擊時,由於冒泡原理,事件就會冒泡到ul上,因為ul上有點擊事件,所以事件就會觸發,當然,這里當點擊ul的時候,也是會觸發的,那麼問題就來了,如果我想讓事件代理的效果跟直接給節點的事件效果一樣怎麼辦,比如說只有點擊li才會觸發:
Event對象提供了一個屬性叫target,可以返回事件的目標節點,我們稱為事件源,也就是說,target就可以表示為當前的事件操作的dom,但是不是真正操作dom,當然,這個是有兼容性的,標准瀏覽器用ev.target,IE瀏覽器用event.srcElement,此時只是獲取了當前節點的位置,並不知道是什麼節點名稱,這里我們用nodeName來獲取具體是什麼標簽名,這個返回的是一個大寫的,我們需要轉成小寫再做比較
這樣改下就只有點擊li會觸發事件了,且每次只執行一次dom操作,如果li數量很多的話,將大大減少dom的操作,優化的性能可想而知!
2、上面的例子是說li操作的是同樣的效果,要是每個li被點擊的效果都不一樣,那麼用事件委託還有用嗎?
用事件代理如何優化??
現在是移入li,li變紅,移出li,li變白,這么一個效果,然後點擊按鈕,可以向ul中添加一個li子節點
這是一般的做法,但是你會發現,新增的li是沒有事件的,說明添加子節點的時候,事件沒有一起添加進去,這不是我們想要的結果,那怎麼做呢?一般的解決方案會是這樣,將for循環用一個函數包起來,命名為mHover,如下:
雖然功能實現了,看著還挺好,但實際上無疑是又增加了一個dom操作,在優化性能方面是不可取的,那麼有事件委託的方式,能做到優化嗎?
看上面是用事件委託的方式,新添加的子元素是帶有事件效果的,我們可以發現,當用事件委託的時候,根本就不需要去遍歷元素的子節點,只需要給父級元素添加事件就好了,其他的都是在js裡面的執行,這樣可以大大的減少dom操作,這才是事件委託的精髓所在。
那什麼樣的事件可以用事件委託,什麼樣的事件不可以用呢?
適合用事件委託的事件:
不適合的就有很多了,舉個例子,mousemove,每次都要計算它的位置,非常不好把控,再比如說focus,blur,load,unload之類的,本身就沒用冒泡的特性,自然就不能用事件委託了。
『貳』 DOM事件機制
前言
一、DOM事件級別
DOM級別一共可以分為四個級別:DOM0級、DOM1級、DOM2級和DOM3級。而DOM事件分為3個級別:DOM 0級事件處理,DOM 2級事件處理和DOM 3級事件處理。由於DOM 1級中沒有事件的相關內容,所以沒有DOM 1級事件。
1.DOM 0級事件
el.onclick=function(){}
當希望為同一個元素/標簽綁定多個同類型事件的時候(如給上面的這個btn元素綁定3個點擊事件),是不被允許的。DOM0事件綁定,給元素的事件行為綁定方法,這些方法都是在當前元素事件行為的冒泡階段(或者目標階段)執行的。
2.DOM 2級事件
el.addEventListener(event-name, callback, useCapture)
event-name: 事件名稱,可以是標準的DOM事件
callback: 回調函數,當事件觸發時,函數會被注入一個參數為當前的事件對象 event
useCapture: 默認是false,代表事件句柄在冒泡階段執行
IE9以下的IE瀏覽器不支持 addEventListener()和removeEventListener(),使用 attachEvent()與detachEvent() 代替,因為IE9以下是不支持事件捕獲的,所以也沒有第三個參數,第一個事件名稱前要加on。
3.DOM 3級事件
在DOM 2級事件的基礎上添加了更多的事件類型。
UI事件,當用戶與頁面上的元素交互時觸發,如:load、scroll
焦點事件,當元素獲得或失去焦點時觸發,如:blur、focus
滑鼠事件,當用戶通過滑鼠在頁面執行操作時觸發如:dblclick、mouseup
滾輪事件,當使用滑鼠滾輪或類似設備時觸發,如:mousewheel
文本事件,當在文檔中輸入文本時觸發,如:textInput
鍵盤事件,當用戶通過鍵盤在頁面上執行操作時觸發,如:keydown、keypress
合成事件,當為IME(輸入法編輯器)輸入字元時觸發,如:compositionstart
變動事件,當底層DOM結構發生變化時觸發,如:DOMsubtreeModified
同時DOM3級事件也允許使用者自定義一些事件。
二、DOM事件模型和事件流
DOM事件模型分為捕獲和冒泡。一個事件發生後,會在子元素和父元素之間傳播(propagation)。這種傳播分成三個階段。
(1)捕獲階段:事件從window對象自上而下向目標節點傳播的階段;
(2)目標階段:真正的目標節點正在處理事件的階段;
(3)冒泡階段:事件從目標節點自下而上向window對象傳播的階段。
DOM事件捕獲的具體流程
捕獲是從上到下,事件先從window對象,然後再到document(對象),然後是html標簽(通過document.documentElement獲取html標簽),然後是body標簽(通過document.body獲取body標簽),然後按照普通的html結構一層一層往下傳,最後到達目標元素。
而事件冒泡的流程剛好是事件捕獲的逆過程。
接下來我們看個事件冒泡的例子:
正如我們上面提到的onclick給元素的事件行為綁定方法都是在當前元素事件行為的冒泡階段(或者目標階段)執行的。
三、事件代理(事件委託)
由於事件會在冒泡階段向上傳播到父節點,因此可以把子節點的監聽函數定義在父節點上,由父節點的監聽函數統一處理多個子元素的事件。這種方法叫做事件的代理(delegation)。
1.優點
減少內存消耗,提高性能
假設有一個列表,列表之中有大量的列表項,我們需要在點擊每個列表項的時候響應一個事件
如果給每個列表項一一都綁定一個函數,那對於內存消耗是非常大的,效率上需要消耗很多性能。藉助事件代理,我們只需要給父容器ul綁定方法即可,這樣不管點擊的是哪一個後代元素,都會根據冒泡傳播的傳遞機制,把容器的click行為觸發,然後把對應的方法執行,根據事件源,我們可以知道點擊的是誰,從而完成不同的事。
動態綁定事件
在很多時候,我們需要通過用戶操作動態的增刪列表項元素,如果一開始給每個子元素綁定事件,那麼在列表發生變化時,就需要重新給新增的元素綁定事件,給即將刪去的元素解綁事件,如果用事件代理就會省去很多這樣麻煩。
2.如何實現
接下來我們來實現上例中父層元素 #list 下的 li 元素的事件委託到它的父層元素上:
四、Event對象常見的應用
event. preventDefault()
如果調用這個方法,默認事件行為將不再觸發。什麼是默認事件呢?例如表單一點擊提交按鈕(submit)跳轉頁面、a標簽默認頁面跳轉或是錨點定位等。
很多時候我們使用a標簽僅僅是想當做一個普通的按鈕,點擊實現一個功能,不想頁面跳轉,也不想錨點定位。
也可以通過JS方法來阻止,給其click事件綁定方法,當我們點擊A標簽的時候,先觸發click事件,其次才會執行自己的默認行為
接下來我們看個例子:輸入框最多隻能輸入六個字元,如何實現?
event.stopPropagation() & event.stopImmediatePropagation()
event.stopPropagation() 方法阻止事件冒泡到父元素,阻止任何父事件處理程序被執行。上面提到事件冒泡階段是指事件從目標節點自下而上向window對象傳播的階段。
我們在例4的inner元素click事件上,添加event.stopPropagation()這句話後,就阻止了父事件的執行,最後只列印了'inner'。
stopImmediatePropagation 既能阻止事件向父元素冒泡,也能阻止元素同事件類型的其它監聽器被觸發。而 stopPropagation 只能實現前者的效果。我們來看個例子:
如上所示,使用 stopImmediatePropagation後,點擊按鈕時,不僅body綁定事件不會觸發,與此同時按鈕的另一個點擊事件也不觸發。
event.target & event.currentTarget
老實說這兩者的區別,並不好用文字描述,我們先來看個例子:
當我們點擊最里層的元素d的時候,會依次輸出:
從輸出中我們可以看到,event.target指向引起觸發事件的元素,而event.currentTarget則是事件綁定的元素,只有被點擊的那個目標元素的event.target才會等於event.currentTarget。也就是說,event.currentTarget始終是監聽事件者,而event.target是事件的真正發出者。
五、參考文章
DOM級別與DOM事件
DOM事件機制解惑
事件模型
JavaScript 事件委託詳解
JavaScript 事件的學與記:stopPropagation 和 stopImmediatePropagation
event.target和event.currentTarget的區別
原文: https://github.com/ljianshu/Blog/issues/44
最後:「相信有很多想學前端的小夥伴,今年年初我花了一個月整理了一份最適合2018年學習的web前端干貨,從最基礎的HTML+CSS+JS到移動端HTML5等都有整理,送給每一位前端小夥伴,53763,1707這里是小白聚集地,歡迎初學和進階中的小夥伴。」
祝大家早日學有所成,拿到滿意offer,快速升職加薪,走上人生巔峰。
『叄』 原生javascript 怎麼事件代理 addeventlistener
事件代理就好比,有一個小隊,共十人,給其中一人綁上一個大喇叭,你要找張三的時候,直接告訴綁上大喇叭的人,大喇叭一喊:咱們隊伍中叫張三的給我出來。這個過程就叫做事件代理。
http://www.cnblogs.com/ARMdong/articles/javascript_delegate.html
『肆』 前端性能優化總結(一)-js、css優化
移動互聯網時代,用戶對於網頁的打開速度要求越來越高。首屏作為直面用戶的第一屏,其重要性不言而喻。優化用戶體驗更是我們前端開發非常需要 focus 的東西之一。
從用戶的角度而言,當打開一個網頁,往往關心的是從輸入完網頁地址後到最後展現完整頁面這個過程需要的時間,這個時間越短,用戶體驗越好。所以作為網頁的開發者,就從輸入url到頁面渲染呈現這個過程中去提升網頁的性能。
所以輸入URL後發生了什麼呢?在瀏覽器中輸入url會經歷域名解析、建立TCP連接、發送http請求、資源解析等步驟。
http緩存優化是網頁性能優化的重要一環,這一部分我會在後續筆記中做一個詳細總結,所以本文暫不多做詳細整理。本文主要從網頁渲染過程、網頁交互以及Vue應用優化三個角度對性能優化做一個小結。
首先談談拿到服務端資源後瀏覽器渲染的流程:
關鍵渲染路徑是瀏覽器將 HTML、CSS、JavaScript 轉換為在屏幕上呈現的像素內容所經歷的一系列步驟。也就是我們剛剛提到的的的瀏覽器渲染流程。
為盡快完成首次渲染,我們需要最大限度減小以下三種可變因素:
首先,DOM 和 CSSOM 通常是並行構建的,所以 CSS 載入不會阻塞 DOM 的解析。
然而,由於 Render Tree 是依賴於 DOM Tree 和 CSSOM Tree 的,
所以他必須等待到 CSSOM Tree 構建完成,也就是 CSS 資源載入完成(或者 CSS 資源載入失敗)後,才能開始渲染。因此,CSS 載入會阻塞 Dom 的渲染。
由此可見,對於 CSSOM 縮小、壓縮以及緩存同樣重要,我們可以從這方面考慮去優化。
當瀏覽器遇到 script 標記時,會阻止解析器繼續操作,直到 CSSOM 構建完畢,JavaScript 才會運行並繼續完成 DOM 構建過程。
當頁面中元素樣式的改變並不影響它在文檔流中的位置時(例如:color、background-color、visibility 等),瀏覽器會將新樣式賦予給元素並重新繪制它,這個過程稱為重繪。
迴流(Reflow)
當 Render Tree 中部分或全部元素的尺寸、結構、或某些屬性發生改變時,瀏覽器重新渲染部分或全部文檔的過程稱為迴流。
有時即使僅僅迴流一個單一的元素,它的父元素以及任何跟隨它的元素也會產生迴流。現代瀏覽器會對頻繁的迴流或重繪操作進行優化:瀏覽器會維護一個隊列,把所有引起迴流和重繪的操作放入隊列中,如果隊列中的任務數量或者時間間隔達到一個閾值的,瀏覽器就會將隊列清空,進行一次批處理,這樣可以把多次迴流和重繪變成一次。
當你訪問以下屬性或方法時,瀏覽器會立刻清空隊列:
因為隊列中可能會有影響到這些屬性或方法返回值的操作,即使你希望獲取的信息與隊列中操作引發的改變無關,瀏覽器也會強行清空隊列,確保你拿到的值是最精確的。
避免頻繁操作樣式,最好一次性重寫 style 屬性,或者將樣式列表定義為 class 並一次性更改 class 屬性。
避免頻繁操作 DOM,創建一個 documentFragment,在它上面應用所有 DOM 操作,最後再把它添加到文檔中。
也可以先為元素設置 display: none,操作結束後再把它顯示出來。因為在 display 屬性為 none 的元素上進行的 DOM 操作不會引發迴流和重繪。
避免頻繁讀取會引發迴流/重繪的屬性,如果確實需要多次使用,就用一個變數緩存起來。
對具有復雜動畫的元素使用絕對定位,使它脫離文檔流,否則會引起父元素及後續元素頻繁迴流。
圖片懶載入在一些圖片密集型的網站中運用比較多,通過圖片懶載入可以讓一些不可視的圖片不去載入,避免一次性載入過多的圖片導致請求阻塞(瀏覽器一般對同一域名下的並發請求的連接數有限制),這樣就可以提高網站的載入速度,提高用戶體驗。
將頁面中的img標簽src指向一張小圖片或者src為空,然後定義data-src(這個屬性可以自定義命名,我才用data-src)屬性指向真實的圖片。src指向一張默認的圖片,否則當src為空時也會向伺服器發送一次請求。可以指向loading的地址。注意,圖片要指定寬高。
當載入頁面時,先把可視區域內的img標簽的data-src屬性值負給src,然後監聽滾動事件,把用戶即將看到的圖片載入。這樣便實現了懶載入。
事件委託其實就是利用JS事件冒泡機制把原本需要綁定在子元素的響應事件(click、keydown……)委託給父元素,讓父元素擔當事件監聽的職務。事件代理的原理是DOM元素的事件冒泡。
優點:
例如有一個列表需要綁定點擊事件,每一個列表項的點擊都需要返回不同的結果。
傳統寫法:
傳統方法會利用for循環遍歷列表為每一個列表元素綁定點擊事件,當列表中元素數量非常龐大時,需要綁定大量的點擊事件,這種方式就會產生性能問題。這種情況下利用事件委託就能很好的解決這個問題。
改用事件委託:
輸入搜索時,可以用防抖debounce等優化方式,減少http請求;
這里以滾動條事件舉例:防抖函數 onscroll 結束時觸發一次,延遲執行
節流函數:只允許一個函數在N秒內執行一次。滾動條調用介面時,可以用節流throttle等優化方式,減少http請求;
下面還是一個簡單的滾動條事件節流函數:節流函數 onscroll 時,每隔一段時間觸發一次,像水滴一樣
參考鏈接: https://zhuanlan.hu.com/p/113864878?from_voters_page=true